OpenSolaris_b135/cmd/praudit/format.c

Compare this file to the similar file:
Show the results in this format:

/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */


#define	_REENTRANT

#include <ctype.h>
#include <errno.h>
#include <grp.h>
#include <libintl.h>
#include <netdb.h>
#include <time.h>
#include <pwd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <wchar.h>

#include <arpa/inet.h>

#include <bsm/audit.h>
#include <bsm/audit_record.h>
#include <bsm/libbsm.h>
#include <security/pam_appl.h>

#include <sys/inttypes.h>
#include <sys/mkdev.h>
#include <sys/types.h>
#include <aclutils.h>

#include "praudit.h"
#include "toktable.h"
#include "adt_xlate.h"

static void	convertascii(char *p, char *c, int size);
static int	convertbinary(char *p, char *c, int size);
static void	eventmodifier2string(au_emod_t emodifier, char *modstring,
    size_t modlen);
static int	do_mtime32(pr_context_t *context, int status, int flag,
    uint32_t scale);
static int	do_mtime64(pr_context_t *context, int status, int flag,
    uint64_t scale);

/*
 * ------------------------------------------------------
 * field widths for arbitrary data token type
 * ------------------------------------------------------
 */
static struct fw {
	char	basic_unit;
	struct {
		char	print_base;
		int	field_width;
	} pwidth[5];
} fwidth[] = {
	/* character data type, 8 bits */
		AUR_CHAR,	AUP_BINARY,	12,
				AUP_OCTAL,	 6,
				AUP_DECIMAL,	 6,
				AUP_HEX,	 6,
				AUP_STRING,	 1,
		AUR_BYTE,	AUP_BINARY,	12,
				AUP_OCTAL,	 6,
				AUP_DECIMAL,	 6,
				AUP_HEX,	 6,
				AUP_STRING,	 1,
		AUR_SHORT,	AUP_BINARY,	20,
				AUP_OCTAL,	10,
				AUP_DECIMAL,	10,
				AUP_HEX,	 8,
				AUP_STRING,	 6,
		AUR_INT32,	AUP_BINARY,	36,
				AUP_OCTAL,	18,
				AUP_DECIMAL,	18,
				AUP_HEX,	12,
				AUP_STRING,	10,
		AUR_INT64,	AUP_BINARY,	68,
				AUP_OCTAL,	34,
				AUP_DECIMAL,	34,
				AUP_HEX,	20,
				AUP_STRING,	20};


static int	numwidthentries = sizeof (fwidth)
			/ sizeof (struct fw);


/*
 * -----------------------------------------------------------------------
 * do_newline:
 *		  Print a newline, if needed according to various formatting
 *		  rules.
 * return codes :   0 - success
 *		:  -1 - error
 * -----------------------------------------------------------------------
 */
int
do_newline(pr_context_t *context, int flag)
{
	int	retstat = 0;

	if (!(context->format & PRF_ONELINE) && (flag == 1))
		retstat = pr_putchar(context, '\n');
	else if (!(context->format & PRF_XMLM))
		retstat = pr_printf(context, "%s", context->SEPARATOR);

	return (retstat);
}

int
open_tag(pr_context_t *context, int tagnum)
{
	int		err = 0;
	token_desc_t	*tag;

	/* no-op if not doing XML format */
	if (!(context->format & PRF_XMLM))
		return (0);

	tag = &tokentable[tagnum];

	/*
	 * First if needed do an implicit finish of a pending open for an
	 * extended tag.  I.e., for the extended tag xxx:
	 *	<xxx a=".." b=".."> ...  </xxx>
	 * -- insert a close bracket after the last attribute
	 * (in other words, when the 1st non-attribute is opened while
	 * this is pending). Note that only one tag could be pending at
	 * a given time -- it couldn't be nested.
	 */
	if (context->pending_flag && (tag->t_type != T_ATTRIBUTE)) {
		/* complete pending extended open */
		err = pr_putchar(context, '>');
		if (err != 0)
			return (err);
		context->pending_flag = 0;
	}

	if (is_header_token(tagnum) || is_file_token(tagnum)) {
		/* File token or new record on new line */
		err = pr_putchar(context, '\n');
	} else if (is_token(tagnum)) {
		/* Each token on new line if possible */
		err = do_newline(context, 1);
	}
	if (err != 0)
		return (err);

	switch (tag->t_type) {
	case T_ATTRIBUTE:
		err = pr_printf(context, " %s=\"", tag->t_tagname);
		break;
	case T_ELEMENT:
		err = pr_printf(context, "<%s>", tag->t_tagname);
		break;
	case T_ENCLOSED:
		err = pr_printf(context, "<%s", tag->t_tagname);
		break;
	case T_EXTENDED:
		err = pr_printf(context, "<%s", tag->t_tagname);
		if (err == 0)
			context->pending_flag = tagnum;
		break;
	default:
		break;
	}

	if (is_header_token(tagnum) && (err == 0))
		context->current_rec = tagnum;	/* set start of new record */

	return (err);
}

/*
 * Do an implicit close of a record when needed.
 */
int
check_close_rec(pr_context_t *context, int tagnum)
{
	int	err = 0;

	/* no-op if not doing XML format */
	if (!(context->format & PRF_XMLM))
		return (0);

	/*
	 * If we're opening a header or the file token (i.e., starting a new
	 * record), if there's a current record in progress do an implicit
	 * close of it.
	 */
	if ((is_header_token(tagnum) || is_file_token(tagnum)) &&
	    context->current_rec) {
		err = do_newline(context, 1);
		if (err == 0)
			err = close_tag(context, context->current_rec);
	}

	return (err);
}

/*
 * explicit finish of a pending open for an extended tag.
 */
int
finish_open_tag(pr_context_t *context)
{
	int	err = 0;

	/* no-op if not doing XML format */
	if (!(context->format & PRF_XMLM))
		return (0);

	if (context->pending_flag) {
		/* complete pending extended open */
		err = pr_putchar(context, '>');
		if (err == 0)
			context->pending_flag = 0;
	}
	return (err);
}

int
close_tag(pr_context_t *context, int tagnum)
{
	int		err = 0;
	token_desc_t	*tag;

	/* no-op if not doing XML format */
	if (!(context->format & PRF_XMLM))
		return (0);

	tag = &tokentable[tagnum];

	switch (tag->t_type) {
	case T_ATTRIBUTE:
		err = pr_putchar(context, '\"');
		break;
	case T_ELEMENT:
		err = pr_printf(context, "</%s>", tag->t_tagname);
		break;
	case T_ENCLOSED:
		err = pr_printf(context, "/>");
		break;
	case T_EXTENDED:
		err = pr_printf(context, "</%s>", tag->t_tagname);
		break;
	default:
		break;
	}

	if (is_header_token(tagnum) && (err == 0))
		context->current_rec = 0;	/* closing rec; none current */

	return (err);
}

/*
 * -----------------------------------------------------------------------
 * process_tag:
 *		  Calls the routine corresponding to the tag
 *		  Note that to use this mechanism, all such routines must
 *		  take 2 ints for their parameters; the first of these is
 *		  the current status.
 *
 *		  flag = 1 for newline / delimiter, else 0
 * return codes : -1 - error
 *		:  0 - successful
 * -----------------------------------------------------------------------
 */
int
process_tag(pr_context_t *context, int tagnum, int status, int flag)
{
	int retstat;

	retstat = status;

	if (retstat)
		return (retstat);

	if ((tagnum > 0) && (tagnum <= MAXTAG) &&
	    (tokentable[tagnum].func != NOFUNC)) {
		retstat = open_tag(context, tagnum);
		if (!retstat)
			retstat = (*tokentable[tagnum].func)(context, status,
			    flag);
		if (!retstat)
			retstat = close_tag(context, tagnum);
		return (retstat);
	}
	/* here if token id is not in table */
	(void) fprintf(stderr, gettext("praudit: No code associated with "
	    "tag id %d\n"), tagnum);
	return (0);
}

void
get_Hname(uint32_t addr, char *buf, size_t buflen)
{
	extern char	*inet_ntoa(const struct in_addr);
	struct hostent *phe;
	struct in_addr ia;

	phe = gethostbyaddr((const char *)&addr, 4, AF_INET);
	if (phe == (struct hostent *)0) {
		ia.s_addr = addr;
		(void) snprintf(buf, buflen, "%s", inet_ntoa(ia));
		return;
	}
	ia.s_addr = addr;
	(void) snprintf(buf, buflen, "%s", phe->h_name);
}

void
get_Hname_ex(uint32_t *addr, char *buf, size_t buflen)
{
	struct hostent *phe;
	int err;

	phe = getipnodebyaddr((const void *)addr, 16, AF_INET6, &err);

	if (phe == (struct hostent *)0) {
		(void) inet_ntop(AF_INET6, (void *)addr, buf, buflen);
	} else
		(void) snprintf(buf, buflen, "%s", phe->h_name);

	if (phe)
		freehostent(phe);
}

int
pa_hostname(pr_context_t *context, int status, int flag)
{
	int	returnstat;
	uint32_t	ip_addr;
	struct in_addr ia;
	uval_t uval;
	char	buf[256];

	if (status <  0)
		return (status);

	if ((returnstat = pr_adr_char(context, (char *)&ip_addr, 4)) != 0)
		return (returnstat);

	uval.uvaltype = PRA_STRING;

	if (!(context->format & PRF_RAWM)) {
		uval.string_val = buf;
		get_Hname(ip_addr, buf, sizeof (buf));
		returnstat = pa_print(context, &uval, flag);
	} else {
		ia.s_addr = ip_addr;
		if ((uval.string_val = inet_ntoa(ia)) == NULL)
			return (-1);
		returnstat = pa_print(context, &uval, flag);
	}
	return (returnstat);
}

int
pa_hostname_ex(pr_context_t *context, int status, int flag)
{
	int	returnstat;
	uint32_t	ip_type;
	uint32_t	ip_addr[4];
	struct in_addr ia;
	char buf[256];
	uval_t uval;

	if (status <  0)
		return (status);

	/* get ip type */
	if ((returnstat = pr_adr_int32(context, (int32_t *)&ip_type, 1)) != 0)
		return (returnstat);

	/* only IPv4 and IPv6 addresses are legal */
	if ((ip_type != AU_IPv4) && (ip_type != AU_IPv6))
		return (-1);

	/* get ip address */
	if ((returnstat = pr_adr_char(context, (char *)ip_addr, ip_type)) != 0)
			return (returnstat);

	if ((returnstat = open_tag(context, TAG_HOSTID)) != 0)
		return (returnstat);

	uval.uvaltype = PRA_STRING;
	if (ip_type == AU_IPv4) {		/* ipv4 address */
		if (!(context->format & PRF_RAWM)) {
			uval.string_val = buf;
			get_Hname(ip_addr[0], buf, sizeof (buf));
			returnstat = pa_print(context, &uval, flag);
		} else {
			ia.s_addr = ip_addr[0];
			if ((uval.string_val = inet_ntoa(ia)) == NULL)
				return (-1);
			returnstat = pa_print(context, &uval, flag);
		}
	} else if (ip_type == AU_IPv6) {	/* IPv6 addresss (128 bits) */
		if (!(context->format & PRF_RAWM)) {
			uval.string_val = buf;
			get_Hname_ex(ip_addr, buf, sizeof (buf));
			returnstat = pa_print(context, &uval, flag);
		} else {
			uval.string_val = (char *)buf;
			(void) inet_ntop(AF_INET6, (void *)ip_addr, buf,
			    sizeof (buf));
			returnstat = pa_print(context, &uval, flag);
		}
	}

	if (returnstat != 0)
		return (returnstat);
	return (close_tag(context, TAG_HOSTID));
}

int
pa_hostname_so(pr_context_t *context, int status, int flag)
{
	int		returnstat;
	short		ip_type;
	ushort_t	ip_port;
	uint32_t	ip_addr[4];
	struct in_addr ia;
	char buf[256];
	uval_t uval;

	if (status <  0)
		return (status);

	/* get ip type */
	if ((returnstat = pr_adr_short(context, &ip_type, 1)) != 0)
		return (returnstat);

	/* only IPv4 and IPv6 addresses are legal */
	if ((ip_type != AU_IPv4) && (ip_type != AU_IPv6))
		return (-1);

	/* get local ip port */
	if ((returnstat = pr_adr_u_short(context, &ip_port, 1)) != 0)
		return (returnstat);

	if ((returnstat = open_tag(context, TAG_SOCKEXLPORT)) != 0)
		return (returnstat);

	uval.uvaltype = PRA_STRING;
	uval.string_val = hexconvert((char *)&ip_port, sizeof (ip_port),
	    sizeof (ip_port));
	if (uval.string_val) {
		returnstat = pa_print(context, &uval, 0);
		free(uval.string_val);
	} else
		returnstat = -1;
	if (returnstat)
		return (returnstat);

	if ((returnstat = close_tag(context, TAG_SOCKEXLPORT)) != 0)
		return (returnstat);

	/* get local ip address */
	if ((returnstat = pr_adr_char(context, (char *)ip_addr, ip_type)) != 0)
			return (returnstat);

	if ((returnstat = open_tag(context, TAG_SOCKEXLADDR)) != 0)
		return (returnstat);

	if (ip_type == AU_IPv4) {		/* ipv4 address */

		if (!(context->format & PRF_RAWM)) {
			uval.string_val = buf;
			get_Hname(ip_addr[0], buf, sizeof (buf));
			returnstat = pa_print(context, &uval, 0);
		} else {
			ia.s_addr = ip_addr[0];
			if ((uval.string_val = inet_ntoa(ia)) == NULL)
				return (-1);
			returnstat = pa_print(context, &uval, 0);
		}

	} else if (ip_type == AU_IPv6) {	/* IPv6 addresss (128 bits) */

		if (!(context->format & PRF_RAWM)) {
			uval.string_val = buf;
			get_Hname_ex(ip_addr, buf, sizeof (buf));
			returnstat = pa_print(context, &uval, 0);
		} else {
			uval.string_val = (char *)buf;
			(void) inet_ntop(AF_INET6, (void *)ip_addr, buf,
			    sizeof (buf));
			returnstat = pa_print(context, &uval, 0);
		}
	} else
		returnstat = -1;

	if (returnstat)
		return (returnstat);

	if ((returnstat = close_tag(context, TAG_SOCKEXLADDR)) != 0)
		return (returnstat);

	/* get foreign ip port */
	if ((returnstat = pr_adr_u_short(context, &ip_port, 1)) != 0)
		return (returnstat);

	if ((returnstat = open_tag(context, TAG_SOCKEXFPORT)) != 0)
		return (returnstat);

	uval.string_val = hexconvert((char *)&ip_port, sizeof (ip_port),
	    sizeof (ip_port));
	if (uval.string_val) {
		returnstat = pa_print(context, &uval, 0);
		free(uval.string_val);
	} else
		returnstat = -1;

	if (returnstat)
		return (returnstat);

	if ((returnstat = close_tag(context, TAG_SOCKEXFPORT)) != 0)
		return (returnstat);

	/* get foreign ip address */
	if ((returnstat = pr_adr_char(context, (char *)ip_addr, ip_type)) != 0)
			return (returnstat);

	if ((returnstat = open_tag(context, TAG_SOCKEXFADDR)) != 0)
		return (returnstat);

	if (ip_type == AU_IPv4) {		/* ipv4 address */

		if (!(context->format & PRF_RAWM)) {
			uval.string_val = buf;
			get_Hname(ip_addr[0], buf, sizeof (buf));
			returnstat = pa_print(context, &uval, flag);
		} else {
			ia.s_addr = ip_addr[0];
			if ((uval.string_val = inet_ntoa(ia)) == NULL)
				return (-1);
			returnstat = pa_print(context, &uval, flag);
		}

	} else if (ip_type == AU_IPv6) {	/* IPv6 addresss (128 bits) */

		if (!(context->format & PRF_RAWM)) {
			uval.string_val = buf;
			get_Hname_ex(ip_addr, buf, sizeof (buf));
			returnstat = pa_print(context, &uval, flag);
		} else {
			uval.string_val = (char *)buf;
			(void) inet_ntop(AF_INET6, (void *)ip_addr, buf,
			    sizeof (buf));
			returnstat = pa_print(context, &uval, flag);
		}
	} else
		returnstat = -1;

	if (returnstat)
		return (returnstat);

	if ((returnstat = close_tag(context, TAG_SOCKEXFADDR)) != 0)
		return (returnstat);

	return (returnstat);
}


#define	NBITSMAJOR64	32	/* # of major device bits in 64-bit Solaris */
#define	NBITSMINOR64	32	/* # of minor device bits in 64-bit Solaris */
#define	MAXMAJ64	0xfffffffful	/* max major value */
#define	MAXMIN64	0xfffffffful	/* max minor value */

#define	NBITSMAJOR32	14	/* # of SVR4 major device bits */
#define	NBITSMINOR32	18	/* # of SVR4 minor device bits */
#define	NMAXMAJ32	0x3fff	/* SVR4 max major value */
#define	NMAXMIN32	0x3ffff	/* MAX minor for 3b2 software drivers. */


static int32_t
minor_64(uint64_t dev)
{
	if (dev == NODEV) {
		errno = EINVAL;
		return (NODEV);
	}
	return (int32_t)(dev & MAXMIN64);
}

static int32_t
major_64(uint64_t dev)
{
	uint32_t maj;

	maj = (uint32_t)(dev >> NBITSMINOR64);

	if (dev == NODEV || maj > MAXMAJ64) {
		errno = EINVAL;
		return (NODEV);
	}
	return (int32_t)(maj);
}

static int32_t
minor_32(uint32_t dev)
{
	if (dev == NODEV) {
		errno = EINVAL;
		return (NODEV);
	}
	return (int32_t)(dev & MAXMIN32);
}

static int32_t
major_32(uint32_t dev)
{
	uint32_t maj;

	maj = (uint32_t)(dev >> NBITSMINOR32);

	if (dev == NODEV || maj > MAXMAJ32) {
		errno = EINVAL;
		return (NODEV);
	}
	return (int32_t)(maj);
}


/*
 * -----------------------------------------------------------------------
 * pa_tid() 	: Process terminal id and display contents
 * return codes	: -1 - error
 *		:  0 - successful
 *
 *	terminal id port		adr_int32
 *	terminal id machine		adr_int32
 * -----------------------------------------------------------------------
 */
int
pa_tid32(pr_context_t *context, int status, int flag)
{
	int	returnstat;
	int32_t dev_maj_min;
	uint32_t	ip_addr;
	struct in_addr ia;
	char	*ipstring;
	char	buf[256];
	uval_t	uval;

	if (status <  0)
		return (status);

	if ((returnstat = pr_adr_int32(context, &dev_maj_min, 1)) != 0)
		return (returnstat);

	if ((returnstat = pr_adr_char(context, (char *)&ip_addr, 4)) != 0)
		return (returnstat);

	uval.uvaltype = PRA_STRING;
	uval.string_val = buf;

	if (!(context->format & PRF_RAWM)) {
		char	hostname[256];

		get_Hname(ip_addr, hostname, sizeof (hostname));
		(void) snprintf(buf, sizeof (buf), "%d %d %s",
		    major_32(dev_maj_min),
		    minor_32(dev_maj_min),
		    hostname);
		return (pa_print(context, &uval, flag));
	}

	ia.s_addr = ip_addr;
	if ((ipstring = inet_ntoa(ia)) == NULL)
		return (-1);

	(void) snprintf(buf, sizeof (buf), "%d %d %s", major_32(dev_maj_min),
	    minor_32(dev_maj_min),
	    ipstring);

	return (pa_print(context, &uval, flag));
}

int
pa_tid32_ex(pr_context_t *context, int status, int flag)
{
	int		returnstat;
	int32_t		dev_maj_min;
	uint32_t	ip_addr[16];
	uint32_t	ip_type;
	struct in_addr	ia;
	char		*ipstring;
	char		hostname[256];
	char		buf[256];
	char		tbuf[256];
	uval_t		uval;

	if (status <  0)
		return (status);

	/* get port info */
	if ((returnstat = pr_adr_int32(context, &dev_maj_min, 1)) != 0)
		return (returnstat);

	/* get address type */
	if ((returnstat = pr_adr_u_int32(context, &ip_type, 1)) != 0)
		return (returnstat);

	/* legal address types are either AU_IPv4 or AU_IPv6 only */
	if ((ip_type != AU_IPv4) && (ip_type != AU_IPv6))
		return (-1);

	/* get address (4/16) */
	if ((returnstat = pr_adr_char(context, (char *)ip_addr, ip_type)) != 0)
		return (returnstat);

	uval.uvaltype = PRA_STRING;
	if (ip_type == AU_IPv4) {
		uval.string_val = buf;

		if (!(context->format & PRF_RAWM)) {
			get_Hname(ip_addr[0], hostname, sizeof (hostname));
			(void) snprintf(buf, sizeof (buf), "%d %d %s",
			    major_32(dev_maj_min), minor_32(dev_maj_min),
			    hostname);
			return (pa_print(context, &uval, flag));
		}

		ia.s_addr = ip_addr[0];
		if ((ipstring = inet_ntoa(ia)) == NULL)
			return (-1);

		(void) snprintf(buf, sizeof (buf), "%d %d %s",
		    major_32(dev_maj_min), minor_32(dev_maj_min), ipstring);

		return (pa_print(context, &uval, flag));
	} else {
		uval.string_val = buf;

		if (!(context->format & PRF_RAWM)) {
			get_Hname_ex(ip_addr, hostname, sizeof (hostname));
			(void) snprintf(buf, sizeof (buf), "%d %d %s",
			    major_32(dev_maj_min), minor_32(dev_maj_min),
			    hostname);
			return (pa_print(context, &uval, flag));
		}

		(void) inet_ntop(AF_INET6, (void *) ip_addr, tbuf,
		    sizeof (tbuf));

		(void) snprintf(buf, sizeof (buf), "%d %d %s",
		    major_32(dev_maj_min), minor_32(dev_maj_min), tbuf);

		return (pa_print(context, &uval, flag));
	}
}

int
pa_ip_addr(pr_context_t *context, int status, int flag)
{
	int		returnstat;
	uval_t		uval;
	uint32_t	ip_addr[4];
	uint32_t	ip_type;
	struct in_addr	ia;
	char		*ipstring;
	char		hostname[256];
	char		buf[256];
	char		tbuf[256];

	if (status <  0)
		return (status);

	/* get address type */
	if ((returnstat = pr_adr_u_int32(context, &ip_type, 1)) != 0)
		return (returnstat);

	/* legal address type is AU_IPv4 or AU_IPv6 */
	if ((ip_type != AU_IPv4) && (ip_type != AU_IPv6))
		return (-1);

	/* get address (4/16) */
	if ((returnstat = pr_adr_char(context, (char *)ip_addr, ip_type)) != 0)
		return (returnstat);

	uval.uvaltype = PRA_STRING;
	if (ip_type == AU_IPv4) {
		uval.string_val = buf;

		if (!(context->format & PRF_RAWM)) {
			get_Hname(ip_addr[0], hostname, sizeof (hostname));
			(void) snprintf(buf, sizeof (buf), "%s", hostname);
			return (pa_print(context, &uval, flag));
		}

		ia.s_addr = ip_addr[0];
		if ((ipstring = inet_ntoa(ia)) == NULL)
			return (-1);

		(void) snprintf(buf, sizeof (buf), "%s", ipstring);

		return (pa_print(context, &uval, flag));
	} else {
		uval.string_val = buf;

		if (!(context->format & PRF_RAWM)) {
			get_Hname_ex(ip_addr, hostname, sizeof (hostname));
			(void) snprintf(buf, sizeof (buf), "%s",
			    hostname);
			return (pa_print(context, &uval, flag));
		}

		(void) inet_ntop(AF_INET6, (void *) ip_addr, tbuf,
		    sizeof (tbuf));

		(void) snprintf(buf, sizeof (buf), "%s", tbuf);

		return (pa_print(context, &uval, flag));
	}

}

int
pa_tid64(pr_context_t *context, int status, int flag)
{
	int	returnstat;
	int64_t dev_maj_min;
	uint32_t	ip_addr;
	struct in_addr ia;
	char	*ipstring;
	char	buf[256];
	uval_t	uval;

	if (status <  0)
		return (status);

	if ((returnstat = pr_adr_int64(context, &dev_maj_min, 1)) != 0)
		return (returnstat);

	if ((returnstat = pr_adr_char(context, (char *)&ip_addr, 4)) != 0)
		return (returnstat);

	uval.uvaltype = PRA_STRING;
	uval.string_val = buf;

	if (!(context->format & PRF_RAWM)) {
		char	hostname[256];

		get_Hname(ip_addr, hostname, sizeof (hostname));
		(void) snprintf(buf, sizeof (buf), "%d %d %s",
		    major_64(dev_maj_min), minor_64(dev_maj_min), hostname);
		return (pa_print(context, &uval, flag));
	}

	ia.s_addr = ip_addr;
	if ((ipstring = inet_ntoa(ia)) == NULL)
		return (-1);

	(void) snprintf(buf, sizeof (buf), "%d %d %s",
	    major_64(dev_maj_min), minor_64(dev_maj_min), ipstring);

	return (pa_print(context, &uval, flag));
}

int
pa_tid64_ex(pr_context_t *context, int status, int flag)
{
	int		returnstat;
	int64_t		dev_maj_min;
	uint32_t	ip_addr[4];
	uint32_t	ip_type;
	struct in_addr	ia;
	char		*ipstring;
	char		hostname[256];
	char		buf[256];
	char		tbuf[256];
	uval_t		uval;

	if (status <  0)
		return (status);

	/* get port info */
	if ((returnstat = pr_adr_int64(context, &dev_maj_min, 1)) != 0)
		return (returnstat);

	/* get address type */
	if ((returnstat = pr_adr_u_int32(context, &ip_type, 1)) != 0)
		return (returnstat);

	/* legal address types are either AU_IPv4 or AU_IPv6 only */
	if ((ip_type != AU_IPv4) && (ip_type != AU_IPv6))
		return (-1);

	/* get address (4/16) */
	if ((returnstat = pr_adr_char(context, (char *)&ip_addr, ip_type)) != 0)
		return (returnstat);

	uval.uvaltype = PRA_STRING;
	if (ip_type == AU_IPv4) {
		uval.string_val = buf;

		if (!(context->format & PRF_RAWM)) {
			get_Hname(ip_addr[0], hostname, sizeof (hostname));
			uval.string_val = buf;
			(void) snprintf(buf, sizeof (buf), "%d %d %s",
			    major_64(dev_maj_min), minor_64(dev_maj_min),
			    hostname);
			return (pa_print(context, &uval, flag));
		}

		ia.s_addr = ip_addr[0];
		if ((ipstring = inet_ntoa(ia)) == NULL)
			return (-1);

		(void) snprintf(buf, sizeof (buf), "%d %d %s",
		    major_64(dev_maj_min), minor_64(dev_maj_min), ipstring);

		return (pa_print(context, &uval, flag));
	} else {
		uval.string_val = buf;

		if (!(context->format & PRF_RAWM)) {
			get_Hname_ex(ip_addr, hostname, sizeof (hostname));
			(void) snprintf(buf, sizeof (buf), "%d %d %s",
			    major_64(dev_maj_min), minor_64(dev_maj_min),
			    hostname);
			return (pa_print(context, &uval, flag));
		}

		(void) inet_ntop(AF_INET6, (void *)ip_addr, tbuf,
		    sizeof (tbuf));

		(void) snprintf(buf, sizeof (buf), "%d %d %s",
		    major_64(dev_maj_min), minor_64(dev_maj_min), tbuf);

		return (pa_print(context, &uval, flag));
	}
}


/*
 * ----------------------------------------------------------------
 * findfieldwidth:
 * Returns the field width based on the basic unit and print mode.
 * This routine is called to determine the field width for the
 * data items in the arbitrary data token where the tokens are
 * to be printed in more than one line.  The field width can be
 * found in the fwidth structure.
 *
 * Input parameters:
 * basicunit	Can be one of AUR_CHAR, AUR_BYTE, AUR_SHORT,
 *		AUR_INT32, or AUR_INT64
 * howtoprint	Print mode. Can be one of AUP_BINARY, AUP_OCTAL,
 *		AUP_DECIMAL, or AUP_HEX.
 * ----------------------------------------------------------------
 */
int
findfieldwidth(char basicunit, char howtoprint)
{
	int	i, j;

	for (i = 0; i < numwidthentries; i++) {
		if (fwidth[i].basic_unit == basicunit) {
			for (j = 0; j <= 4; j++) {
				if (fwidth[i].pwidth[j].print_base ==
				    howtoprint) {
					return (
					    fwidth[i].pwidth[j].field_width);
				}
			}
			/*
			 * if we got here, then we didn't get what we were after
			 */
			return (0);
		}
	}
	/* if we got here, we didn't get what we wanted either */
	return (0);
}


/*
 * -----------------------------------------------------------------------
 * pa_cmd: Retrieves the cmd item from the input stream.
 * return codes : -1 - error
 *		:  0 - successful
 * -----------------------------------------------------------------------
 */
int
pa_cmd(pr_context_t *context, int status, int flag)
{
	char	*cmd;  /* cmd */
	short	length;
	int	returnstat;
	uval_t	uval;

	/*
	 * We need to know how much space to allocate for our string, so
	 * read the length first, then call pr_adr_char to read those bytes.
	 */
	if (status >= 0) {
		if (pr_adr_short(context, &length, 1) == 0) {
			if ((cmd = (char *)malloc(length + 1)) == NULL)
				return (-1);
			if (pr_adr_char(context, cmd, length) == 0) {
				uval.uvaltype = PRA_STRING;
				uval.string_val = cmd;
				returnstat = pa_print(context, &uval, flag);
			} else {
				returnstat = -1;
			}
			free(cmd);
			return (returnstat);
		} else
			return (-1);
	} else
		return (status);
}



/*
 * -----------------------------------------------------------------------
 * pa_adr_byte	: Issues pr_adr_char to retrieve the next ADR item from
 *		  the input stream pointed to by audit_adr, and prints it
 *		  as an integer if status >= 0
 * return codes : -1 - error
 *		:  0 - successful
 * -----------------------------------------------------------------------
 */
int
pa_adr_byte(pr_context_t *context, int status, int flag)
{
	char	c;
	uval_t	uval;

	if (status >= 0) {
		if (pr_adr_char(context, &c, 1) == 0) {
			uval.uvaltype = PRA_BYTE;
			uval.char_val = c;
			return (pa_print(context, &uval, flag));
		} else
			return (-1);
	} else
		return (status);
}

/*
 * -----------------------------------------------------------------------
 * pa_adr_charhex: Issues pr_adr_char to retrieve the next ADR item from
 *			the input stream pointed to by audit_adr, and prints it
 *			in hexadecimal if status >= 0
 * return codes  : -1 - error
 *		 :  0 - successful
 * -----------------------------------------------------------------------
 */
int
pa_adr_charhex(pr_context_t *context, int status, int flag)
{
	char	p[2];
	int	returnstat;
	uval_t	uval;

	if (status >= 0) {
		p[0] = p[1] = 0;

		if ((returnstat = pr_adr_char(context, p, 1)) == 0) {
			uval.uvaltype = PRA_STRING;
			uval.string_val = hexconvert(p, sizeof (char),
			    sizeof (char));
			if (uval.string_val) {
				returnstat = pa_print(context, &uval, flag);
				free(uval.string_val);
			}
		}
		return (returnstat);
	} else
		return (status);
}

/*
 * -----------------------------------------------------------------------
 * pa_adr_int32	: Issues pr_adr_int32 to retrieve the next ADR item from the
 *		  input stream pointed to by audit_adr, and prints it
 *		  if status >= 0
 * return codes : -1 - error
 *		:  0 - successful
 * -----------------------------------------------------------------------
 */
int
pa_adr_int32(pr_context_t *context, int status, int flag)
{
	int32_t	c;
	uval_t	uval;

	if (status >= 0) {
		if (pr_adr_int32(context, &c, 1) == 0) {
			uval.uvaltype = PRA_INT32;
			uval.int32_val = c;
			return (pa_print(context, &uval, flag));
		} else
			return (-1);
	} else
		return (status);
}




/*
 * -----------------------------------------------------------------------
 * pa_adr_int64	: Issues pr_adr_int64 to retrieve the next ADR item from the
 *		  input stream pointed to by audit_adr, and prints it
 *		  if status >= 0
 * return codes : -1 - error
 *		:  0 - successful
 * -----------------------------------------------------------------------
 */
int
pa_adr_int64(pr_context_t *context, int status, int flag)
{
	int64_t	c;
	uval_t	uval;

	if (status >= 0) {
		if (pr_adr_int64(context, &c, 1) == 0) {
			uval.uvaltype = PRA_INT64;
			uval.int64_val = c;
			return (pa_print(context, &uval, flag));
		} else
			return (-1);
	} else
		return (status);
}

/*
 * -----------------------------------------------------------------------
 * pa_adr_int64hex: Issues pr_adr_int64 to retrieve the next ADR item from the
 *			input stream pointed to by audit_adr, and prints it
 *			in hexadecimal if status >= 0
 * return codes  : -1 - error
 *		:  0 - successful
 * -----------------------------------------------------------------------
 */
int
pa_adr_int32hex(pr_context_t *context, int status, int flag)
{
	int32_t	l;
	int	returnstat;
	uval_t	uval;

	if (status >= 0) {
		if ((returnstat = pr_adr_int32(context, &l, 1)) == 0) {
			uval.uvaltype = PRA_HEX32;
			uval.int32_val = l;
			returnstat = pa_print(context, &uval, flag);
		}
		return (returnstat);
	} else
		return (status);
}

/*
 * -----------------------------------------------------------------------
 * pa_adr_int64hex: Issues pr_adr_int64 to retrieve the next ADR item from the
 *			input stream pointed to by audit_adr, and prints it
 *			in hexadecimal if status >= 0
 * return codes  : -1 - error
 *		:  0 - successful
 * -----------------------------------------------------------------------
 */
int
pa_adr_int64hex(pr_context_t *context, int status, int flag)
{
	int64_t	l;
	int	returnstat;
	uval_t	uval;

	if (status >= 0) {
		if ((returnstat = pr_adr_int64(context, &l, 1)) == 0) {
			uval.uvaltype = PRA_HEX64;
			uval.int64_val = l;
			returnstat = pa_print(context, &uval, flag);
		}
		return (returnstat);
	} else
		return (status);
}


/*
 * -------------------------------------------------------------------
 * bu2string: Maps a print basic unit type to a string.
 * returns  : The string mapping or "unknown basic unit type".
 * -------------------------------------------------------------------
 */
char *
bu2string(char basic_unit)
{
	register int	i;

	struct bu_map_ent {
		char	basic_unit;
		char	*string;
	};

	/*
	 * TRANSLATION_NOTE
	 * These names are data units when displaying the arbitrary data
	 * token.
	 */

	static struct bu_map_ent bu_map[] = {
				{ AUR_BYTE, "byte" },
				{ AUR_CHAR, "char" },
				{ AUR_SHORT, "short" },
				{ AUR_INT32, "int32" },
				{ AUR_INT64, "int64" } 	};

	for (i = 0; i < sizeof (bu_map) / sizeof (struct bu_map_ent); i++)
		if (basic_unit == bu_map[i].basic_unit)
			return (gettext(bu_map[i].string));

	return (gettext("unknown basic unit type"));
}


/*
 * -------------------------------------------------------------------
 * eventmodifier2string: Maps event modifier flags to a readable string.
 * returns: The string mapping or "none".
 * -------------------------------------------------------------------
 */
static void
eventmodifier2string(au_emod_t emodifier, char *modstring, size_t modlen)
{
	register int	i, j;

	struct em_map_ent {
		int	mask;
		char	*string;
	};

	/*
	 * TRANSLATION_NOTE
	 * These abbreviations represent the event modifier field of the
	 * header token.  To gain a better understanding of each modifier,
	 * read
	 * System Administration Guide: Security Services >> Solaris Auditing
	 * at http://docs.sun.com.
	 */

	static struct em_map_ent em_map[] = {
		{ (int)PAD_READ,	"rd" },	/* data read from object */
		{ (int)PAD_WRITE,	"wr" },	/* data written to object */
		{ (int)PAD_SPRIVUSE,	"sp" },	/* successfully used priv */
		{ (int)PAD_FPRIVUSE,	"fp" },	/* failed use of priv */
		{ (int)PAD_NONATTR,	"na" },	/* non-attributable event */
		{ (int)PAD_FAILURE,	"fe" }	/* fail audit event */
	};

	modstring[0] = '\0';

	for (i = 0, j = 0; i < sizeof (em_map) / sizeof (struct em_map_ent);
	    i++) {
		if ((int)emodifier & em_map[i].mask) {
			if (j++)
				(void) strlcat(modstring, ":", modlen);
			(void) strlcat(modstring, em_map[i].string, modlen);
		}
	}
}


/*
 * ---------------------------------------------------------
 * convert_char_to_string:
 *   Converts a byte to string depending on the print mode
 * input	: printmode, which may be one of AUP_BINARY,
 *		  AUP_OCTAL, AUP_DECIMAL, and AUP_HEX
 *		  c, which is the byte to convert
 * output	: p, which is a pointer to the location where
 *		  the resulting string is to be stored
 *  ----------------------------------------------------------
 */

int
convert_char_to_string(char printmode, char c, char *p)
{
	union {
		char	c1[4];
		int	c2;
	} dat;

	dat.c2 = 0;
	dat.c1[3] = c;

	if (printmode == AUP_BINARY)
		(void) convertbinary(p, &c, sizeof (char));
	else if (printmode == AUP_OCTAL)
		(void) sprintf(p, "%o", (int)dat.c2);
	else if (printmode == AUP_DECIMAL)
		(void) sprintf(p, "%d", c);
	else if (printmode == AUP_HEX)
		(void) sprintf(p, "0x%x", (int)dat.c2);
	else if (printmode == AUP_STRING)
		convertascii(p, &c, sizeof (char));
	return (0);
}

/*
 * --------------------------------------------------------------
 * convert_short_to_string:
 * Converts a short integer to string depending on the print mode
 * input	: printmode, which may be one of AUP_BINARY,
 *		AUP_OCTAL, AUP_DECIMAL, and AUP_HEX
 *		c, which is the short integer to convert
 * output	: p, which is a pointer to the location where
 *		the resulting string is to be stored
 * ---------------------------------------------------------------
 */
int
convert_short_to_string(char printmode, short c, char *p)
{
	union {
		short	c1[2];
		int	c2;
	} dat;

	dat.c2 = 0;
	dat.c1[1] = c;

	if (printmode == AUP_BINARY)
		(void) convertbinary(p, (char *)&c, sizeof (short));
	else if (printmode == AUP_OCTAL)
		(void) sprintf(p, "%o", (int)dat.c2);
	else if (printmode == AUP_DECIMAL)
		(void) sprintf(p, "%hd", c);
	else if (printmode == AUP_HEX)
		(void) sprintf(p, "0x%x", (int)dat.c2);
	else if (printmode == AUP_STRING)
		convertascii(p, (char *)&c, sizeof (short));
	return (0);
}

/*
 * ---------------------------------------------------------
 * convert_int32_to_string:
 * Converts a integer to string depending on the print mode
 * input	: printmode, which may be one of AUP_BINARY,
 *		AUP_OCTAL, AUP_DECIMAL, and AUP_HEX
 *		c, which is the integer to convert
 * output	: p, which is a pointer to the location where
 *		the resulting string is to be stored
 * ----------------------------------------------------------
 */
int
convert_int32_to_string(char printmode, int32_t c, char *p)
{
	if (printmode == AUP_BINARY)
		(void) convertbinary(p, (char *)&c, sizeof (int32_t));
	else if (printmode == AUP_OCTAL)
		(void) sprintf(p, "%o", c);
	else if (printmode == AUP_DECIMAL)
		(void) sprintf(p, "%d", c);
	else if (printmode == AUP_HEX)
		(void) sprintf(p, "0x%x", c);
	else if (printmode == AUP_STRING)
		convertascii(p, (char *)&c, sizeof (int));
	return (0);
}

/*
 * ---------------------------------------------------------
 * convert_int64_to_string:
 * Converts a integer to string depending on the print mode
 * input	: printmode, which may be one of AUP_BINARY,
 *		AUP_OCTAL, AUP_DECIMAL, and AUP_HEX
 *		c, which is the integer to convert
 * output	: p, which is a pointer to the location where
 *		the resulting string is to be stored
 * ----------------------------------------------------------
 */
int
convert_int64_to_string(char printmode, int64_t c, char *p)
{
	if (printmode == AUP_BINARY)
		(void) convertbinary(p, (char *)&c, sizeof (int64_t));
	else if (printmode == AUP_OCTAL)
		(void) sprintf(p, "%"PRIo64, c);
	else if (printmode == AUP_DECIMAL)
		(void) sprintf(p, "%"PRId64, c);
	else if (printmode == AUP_HEX)
		(void) sprintf(p, "0x%"PRIx64, c);
	else if (printmode == AUP_STRING)
		convertascii(p, (char *)&c, sizeof (int64_t));
	return (0);
}


/*
 * -----------------------------------------------------------
 * convertbinary:
 * Converts a unit c of 'size' bytes long into a binary string
 * and returns it into the position pointed to by p
 * ------------------------------------------------------------
 */
int
convertbinary(char *p, char *c, int size)
{
	char	*s, *t, *ss;
	int	i, j;

	if ((s = (char *)malloc(8 * size + 1)) == NULL)
		return (0);

	ss = s;

	/* first convert to binary */
	t = s;
	for (i = 0; i < size; i++) {
		for (j = 0; j < 8; j++)
			(void) sprintf(t++, "%d", ((*c >> (7 - j)) & (0x01)));
		c++;
	}
	*t = '\0';

	/* now string leading zero's if any */
	j = strlen(s) - 1;
	for (i = 0; i < j; i++) {
		if (*s != '0')
			break;
			else
			s++;
	}

	/* now copy the contents of s to p */
	t = p;
	for (i = 0; i < (8 * size + 1); i++) {
		if (*s == '\0') {
			*t = '\0';
			break;
		}
		*t++ = *s++;
	}
	free(ss);

	return (1);
}


static char hex[] = "0123456789abcdef";
/*
 * -------------------------------------------------------------------
 * hexconvert	: Converts a string of (size) bytes to hexadecimal, and
 *		returns the hexadecimal string.
 * returns	: - NULL if memory cannot be allocated for the string, or
 *		- pointer to the hexadecimal string if successful
 * -------------------------------------------------------------------
 */
char *
hexconvert(char *c, int size, int chunk)
{
	register char	*s, *t;
	register int	i, j, k;
	int	numchunks;
	int	leftovers;

	if (size <= 0)
		return (NULL);

	if ((s = (char *)malloc((size * 5) + 1)) == NULL)
		return (NULL);

	if (chunk > size || chunk <= 0)
		chunk = size;

	numchunks = size / chunk;
	leftovers = size % chunk;

	t = s;
	for (i = j = 0; i < numchunks; i++) {
		if (j++) {
			*t++ = ' ';
		}
		*t++ = '0';
		*t++ = 'x';
		for (k = 0; k < chunk; k++) {
			*t++ = hex[(uint_t)((uchar_t)*c >> 4)];
			*t++ = hex[(uint_t)((uchar_t)*c & 0xF)];
			c++;
		}
	}

	if (leftovers) {
		*t++ = ' ';
		*t++ = '0';
		*t++ = 'x';
		for (i = 0; i < leftovers; i++) {
			*t++ = hex[(uint_t)((uchar_t)*c >> 4)];
			*t++ = hex[(uint_t)((uchar_t)*c & 0xF)];
			c++;
		}
	}

	*t = '\0';
	return (s);
}


/*
 * -------------------------------------------------------------------
 * htp2string: Maps a print suggestion to a string.
 * returns   : The string mapping or "unknown print suggestion".
 * -------------------------------------------------------------------
 */
char *
htp2string(char print_sugg)
{
	register int	i;

	struct htp_map_ent {
		char	print_sugg;
		char	*print_string;
	};

	/*
	 * TRANSLATION_NOTE
	 * These names are data types when displaying the arbitrary data
	 * token.
	 */

	static struct htp_map_ent htp_map[] = {
				{ AUP_BINARY, "binary" },
				{ AUP_OCTAL, "octal" },
				{ AUP_DECIMAL, "decimal" },
				{ AUP_HEX, "hexadecimal" },
				{ AUP_STRING, "string" } 	};

	for (i = 0; i < sizeof (htp_map) / sizeof (struct htp_map_ent); i++)
		if (print_sugg == htp_map[i].print_sugg)
			return (gettext(htp_map[i].print_string));

	return (gettext("unknown print suggestion"));
}

/*
 * ----------------------------------------------------------------------
 * pa_adr_short: Issues pr_adr_short to retrieve the next ADR item from the
 *		input stream pointed to by audit_adr, and prints it
 *		if status >= 0
 * return codes: -1 - error
 *		:  0 - successful
 * ----------------------------------------------------------------------
 */
int
pa_adr_short(pr_context_t *context, int status, int flag)
{
	short	c;
	uval_t	uval;

	if (status >= 0) {
		if (pr_adr_short(context, &c, 1) == 0) {
			uval.uvaltype = PRA_SHORT;
			uval.short_val = c;
			return (pa_print(context, &uval, flag));
		} else
			return (-1);
	} else
		return (status);
}

/*
 * -----------------------------------------------------------------------
 * pa_adr_shorthex: Issues pr_adr_short to retrieve the next ADR item from the
 *			input stream pointed to by audit_adr, and prints it
 *			in hexadecimal if status >= 0
 * return codes  : -1 - error
 *		:  0 - successful
 * -----------------------------------------------------------------------
 */
int
pa_adr_shorthex(pr_context_t *context, int status, int flag)
{
	short	s;
	int	returnstat;
	uval_t	uval;

	if (status >= 0) {
		if ((returnstat = pr_adr_short(context, &s, 1)) == 0) {
			uval.uvaltype = PRA_STRING;
			uval.string_val = hexconvert((char *)&s, sizeof (s),
			    sizeof (s));
			if (uval.string_val) {
				returnstat = pa_print(context, &uval, flag);
				free(uval.string_val);
			}
		}
		return (returnstat);
	} else
		return (status);
}


/*
 * -----------------------------------------------------------------------
 * pa_adr_string: Retrieves a string from the input stream and prints it
 *		  if status >= 0
 * return codes : -1 - error
 *		:  0 - successful
 * -----------------------------------------------------------------------
 */
int
pa_adr_string(pr_context_t *context, int status, int flag)
{
	char	*c;
	short	length;
	int	returnstat;
	uval_t	uval;

	/*
	 * We need to know how much space to allocate for our string, so
	 * read the length first, then call pr_adr_char to read those bytes.
	 */
	if (status < 0)
		return (status);

	if ((returnstat = pr_adr_short(context, &length, 1)) != 0)
		return (returnstat);
	if ((c = (char *)malloc(length + 1)) == NULL)
		return (-1);
	if ((returnstat = pr_adr_char(context, c, length)) != 0) {
		free(c);
		return (returnstat);
	}

	uval.uvaltype = PRA_STRING;
	uval.string_val = c;
	returnstat = pa_print(context, &uval, flag);
	free(c);
	return (returnstat);
}

/*
 * -----------------------------------------------------------------------
 * pa_file_string: Retrieves a file string from the input stream and prints it
 *		  if status >= 0
 * return codes : -1 - error
 *		:  0 - successful
 * -----------------------------------------------------------------------
 */
int
pa_file_string(pr_context_t *context, int status, int flag)
{
	char	*c;
	char	*p;
	short	length;
	int	returnstat;
	uval_t	uval;

	/*
	 * We need to know how much space to allocate for our string, so
	 * read the length first, then call pr_adr_char to read those bytes.
	 */
	if (status < 0)
		return (status);

	if ((returnstat = pr_adr_short(context, &length, 1)) != 0)
		return (returnstat);
	if ((c = (char *)malloc(length + 1)) == NULL)
		return (-1);
	if ((p = (char *)malloc((length * 4) + 1)) == NULL) {
		free(c);
		return (-1);
	}
	if ((returnstat = pr_adr_char(context, c, length)) != 0) {
		free(c);
		free(p);
		return (returnstat);
	}

	if (is_file_token(context->tokenid))
		context->audit_rec_len += length;

	convertascii(p, c, length - 1);
	uval.uvaltype = PRA_STRING;
	uval.string_val = p;

	if (returnstat == 0)
		returnstat = finish_open_tag(context);

	if (returnstat == 0)
		returnstat = pa_print(context, &uval, flag);

	free(c);
	free(p);
	return (returnstat);
}

static int
pa_putstr_xml(pr_context_t *context, int printable, char *str, size_t len)
{
	int	err;

	if (!printable) {
		/*
		 * Unprintable chars should always be converted to the
		 * visible form. If there are unprintable characters which
		 * require special treatment in xml, those should be
		 * handled here.
		 */
		do {
			err = pr_printf(context, "\\%03o",
			    (unsigned char)*str++);
		} while (err == 0 && --len != 0);
		return (err);
	}
	/* printable characters */
	if (len == 1) {
		/*
		 * check for the special chars only when char size was 1
		 * ie, ignore special chars appear in the middle of multibyte
		 * sequence.
		 */

		/* Escape for XML */
		switch (*str) {
		case '&':
			err = pr_printf(context, "%s", "&amp;");
			break;

		case '<':
			err = pr_printf(context, "%s", "&lt;");
			break;

		case '>':
			err = pr_printf(context, "%s", "&gt;");
			break;

		case '\"':
			err = pr_printf(context, "%s", "&quot;");
			break;

		case '\'':
			err = pr_printf(context, "%s", "&apos;");
			break;

		default:
			err = pr_putchar(context, *str);
			break;
		}
		return (err);
	}
	do {
		err = pr_putchar(context, *str++);
	} while (err == 0 && --len != 0);
	return (err);
}

static int
pa_putstr(pr_context_t *context, int printable, char *str, size_t len)
{
	int	err;

	if (context->format & PRF_XMLM)
		return (pa_putstr_xml(context, printable, str, len));

	if (!printable) {
		do {
			err = pr_printf(context, "\\%03o",
			    (unsigned char)*str++);
		} while (err == 0 && --len != 0);
		return (err);
	}
	do {
		err = pr_putchar(context, *str++);
	} while (err == 0 && --len != 0);
	return (err);
}

int
pa_string(pr_context_t *context, int status, int flag)
{
	int	rstat, wstat;
	int	i, printable, eos;
	int	mlen, rlen;
	int	mbmax = MB_CUR_MAX;
	wchar_t	wc;
	char	mbuf[MB_LEN_MAX + 1];
	char	c;

	if (status < 0)
		return (status);

	rstat = wstat = 0;

	if (mbmax == 1) {
		while (wstat == 0) {
			if ((rstat = pr_adr_char(context, &c, 1)) < 0)
				break;
			if (c == '\0')
				break;
			printable = isprint((unsigned char)c);
			wstat = pa_putstr(context, printable, &c, 1);
		}
		goto done;
	}

	mlen = eos = 0;
	while (wstat == 0) {
		rlen = 0;
		do {
			if (!eos) {
				rstat = pr_adr_char(context, &c, 1);
				if (rstat != 0 || c == '\0')
					eos = 1;
				else
					mbuf[mlen++] = c;
			}
			rlen = mbtowc(&wc, mbuf, mlen);
		} while (!eos && mlen < mbmax && rlen <= 0);

		if (mlen == 0)
			break;	/* end of string */

		if (rlen <= 0) { /* no good sequence */
			rlen = 1;
			printable = 0;
		} else {
			printable = iswprint(wc);
		}
		wstat = pa_putstr(context, printable, mbuf, rlen);
		mlen -= rlen;
		if (mlen > 0) {
			for (i = 0; i < mlen; i++)
				mbuf[i] = mbuf[rlen + i];
		}
	}

done:
	if (wstat == 0)
		wstat = do_newline(context, flag);

	if (wstat == 0 && context->data_mode == FILEMODE)
		(void) fflush(stdout);

	return ((rstat != 0 || wstat != 0) ? -1 : 0);
}

/*
 * -----------------------------------------------------------------------
 * pa_adr_u_int32: Issues pr_adr_u_int32 to retrieve the next ADR item from
 *		  the input stream pointed to by audit_adr, and prints it
 *		  if status = 0
 * return codes : -1 - error
 *		:  0 - successful
 * -----------------------------------------------------------------------
 */


int
pa_adr_u_int32(pr_context_t *context, int status, int flag)
{
	uint32_t c;
	uval_t	uval;

	if (status >= 0) {
		if (pr_adr_u_int32(context, &c, 1) == 0) {
			uval.uvaltype = PRA_UINT32;
			uval.uint32_val = c;
			return (pa_print(context, &uval, flag));
		} else
			return (-1);
	} else
		return (status);
}



/*
 * -----------------------------------------------------------------------
 * pa_adr_u_int64: Issues pr_adr_u_int64 to retrieve the next ADR item from the
 *		  input stream pointed to by audit_adr, and prints it
 *		  if status = 0
 * return codes : -1 - error
 *		:  0 - successful
 * -----------------------------------------------------------------------
 */
int
pa_adr_u_int64(pr_context_t *context, int status, int flag)
{
	uint64_t c;
	uval_t	uval;

	if (status >= 0) {
		if (pr_adr_u_int64(context, &c, 1) == 0) {
			uval.uvaltype = PRA_UINT64;
			uval.uint64_val = c;
			return (pa_print(context, &uval, flag));
		} else
			return (-1);
	} else
		return (status);
}


/*
 * -----------------------------------------------------------------------
 * pa_adr_u_short: Issues pr_adr_u_short to retrieve the next ADR item from
 *			the input stream pointed to by audit_adr, and prints it
 *			if status = 0
 * return codes : -1 - error
 *		:  0 - successful
 * -----------------------------------------------------------------------
 */
int
pa_adr_u_short(pr_context_t *context, int status, int flag)
{
	ushort_t c;
	uval_t	uval;

	if (status >= 0) {
		if (pr_adr_u_short(context, &c, 1) == 0) {
			uval.uvaltype = PRA_USHORT;
			uval.ushort_val = c;
			return (pa_print(context, &uval, flag));
		} else
			return (-1);
	} else
		return (status);
}

/*
 * -----------------------------------------------------------------------
 * pa_reclen: Issues pr_adr_u_long to retrieve the length of the record
 *		  from the input stream pointed to by audit_adr,
 *		  and prints it (unless format is XML) if status = 0
 * return codes : -1 - error
 *		:  0 - successful
 * -----------------------------------------------------------------------
 */
int
pa_reclen(pr_context_t *context, int status)
{
	uint32_t c;
	uval_t	uval;

	if (status >= 0) {
		if ((int)pr_adr_u_int32(context, &c, 1) == 0) {
			context->audit_rec_len = c;

			/* Don't print this for XML format */
			if (context->format & PRF_XMLM) {
				return (0);
			} else {
				uval.uvaltype = PRA_UINT32;
				uval.uint32_val = c;
				return (pa_print(context, &uval, 0));
			}
		} else
			return (-1);
	} else
		return (status);
}

/*
 * -----------------------------------------------------------------------
 * pa_mode	: Issues pr_adr_u_short to retrieve the next ADR item from
 *		the input stream pointed to by audit_adr, and prints it
 *		in octal if status = 0
 * return codes : -1 - error
 *		:  0 - successful
 * -----------------------------------------------------------------------
 */
int
pa_mode(pr_context_t *context, int status, int flag)
{
	uint32_t c;
	uval_t	uval;

	if (status >= 0) {
		if (pr_adr_u_int32(context, &c, 1) == 0) {
			uval.uvaltype = PRA_LOCT;
			uval.uint32_val = c;
			return (pa_print(context, &uval, flag));
		} else
			return (-1);
	} else
		return (status);
}

static int
pa_print_uid(pr_context_t *context, uid_t uid, int status, int flag)
{
	int	returnstat;
	struct passwd *pw;
	uval_t	uval;

	if (status < 0)
		return (status);

	if (!(context->format & PRF_RAWM)) {
		/* get password file entry */
		if ((pw = getpwuid(uid)) == NULL) {
			returnstat = 1;
		} else {
			/* print in ASCII form */
			uval.uvaltype = PRA_STRING;
			uval.string_val = pw->pw_name;
			returnstat = pa_print(context, &uval, flag);
		}
	}
	/* print in integer form */
	if ((context->format & PRF_RAWM) || (returnstat == 1)) {
		uval.uvaltype = PRA_INT32;
		uval.int32_val = uid;
		returnstat = pa_print(context, &uval, flag);
	}
	return (returnstat);
}


/*
 * -----------------------------------------------------------------------
 * pa_pw_uid()	: Issues pr_adr_u_int32 to reads uid from input stream
 *		pointed to by audit_adr, and displays it in either
 *		raw form or its ASCII representation, if status >= 0.
 * return codes : -1 - error
 * 		:  1 - warning, passwd entry not found
 *		:  0 - successful
 * -----------------------------------------------------------------------
 */
int
pa_pw_uid(pr_context_t *context, int status, int flag)
{
	uint32_t uid;

	if (status < 0)
		return (status);

	if (pr_adr_u_int32(context, &uid, 1) != 0)
		/* cannot retrieve uid */
		return (-1);

	return (pa_print_uid(context, uid, status, flag));
}

static int
pa_print_gid(pr_context_t *context, gid_t gid, int status, int flag)
{
	int	returnstat;
	struct group *gr;
	uval_t	uval;

	if (status < 0)
		return (status);

	if (!(context->format & PRF_RAWM)) {
		/* get group file entry */
		if ((gr = getgrgid(gid)) == NULL) {
			returnstat = 1;
		} else {
			/* print in ASCII form */
			uval.uvaltype = PRA_STRING;
			uval.string_val = gr->gr_name;
			returnstat = pa_print(context, &uval, flag);
		}
	}
	/* print in integer form */
	if ((context->format & PRF_RAWM) || (returnstat == 1)) {
		uval.uvaltype = PRA_INT32;
		uval.int32_val = gid;
		returnstat = pa_print(context, &uval, flag);
	}
	return (returnstat);
}


/*
 * -----------------------------------------------------------------------
 * pa_gr_uid()	: Issues pr_adr_u_int32 to reads group uid from input stream
 *			pointed to by audit_adr, and displays it in either
 *			raw form or its ASCII representation, if status >= 0.
 * return codes : -1 - error
 * 		:  1 - warning, passwd entry not found
 *		:  0 - successful
 * -----------------------------------------------------------------------
 */
int
pa_gr_uid(pr_context_t *context, int status, int flag)
{
	uint32_t gid;

	if (status < 0)
		return (status);

	if (pr_adr_u_int32(context, &gid, 1) != 0)
		/* cannot retrieve gid */
		return (-1);

	return (pa_print_gid(context, gid, status, flag));
}


/*
 * -----------------------------------------------------------------------
 * pa_pw_uid_gr_gid()	: Issues pr_adr_u_int32 to reads uid or group uid
 *			from input stream
 *			pointed to by audit_adr, and displays it in either
 *			raw form or its ASCII representation, if status >= 0.
 * return codes : -1 - error
 * 		:  1 - warning, passwd entry not found
 *		:  0 - successful
 * -----------------------------------------------------------------------
 */
int
pa_pw_uid_gr_gid(pr_context_t *context, int status, int flag)
{
	int	returnstat;
	uint32_t	value;
	uval_t		uval;

	if (status < 0)
		return (status);

	/* get value of a_type */
	if ((returnstat = pr_adr_u_int32(context, &value, 1)) != 0)
		return (returnstat);

	if ((returnstat = open_tag(context, TAG_ACLTYPE)) != 0)
		return (returnstat);

	uval.uvaltype = PRA_UINT32;
	uval.uint32_val = value;
	if ((returnstat = pa_print(context, &uval, flag)) != 0)
		return (returnstat);

	if ((returnstat = close_tag(context, TAG_ACLTYPE)) != 0)
		return (returnstat);

	if ((returnstat = open_tag(context, TAG_ACLVAL)) != 0)
		return (returnstat);
	/*
	 * TRANSLATION_NOTE
	 * The "mask" and "other" strings refer to the class mask
	 * and other (or world) entries in an ACL.
	 * The "unrecognized" string refers to an unrecognized ACL
	 * entry.
	 */
	switch (value) {
		case USER_OBJ:
		case USER:
			returnstat = pa_pw_uid(context, returnstat, flag);
			break;
		case GROUP_OBJ:
		case GROUP:
			returnstat = pa_gr_uid(context, returnstat, flag);
			break;
		case CLASS_OBJ:
			returnstat = pr_adr_u_int32(context, &value, 1);
			if (returnstat != 0)
				return (returnstat);

			if (!(context->format & PRF_RAWM)) {
				uval.uvaltype = PRA_STRING;
				uval.string_val = gettext("mask");
				returnstat = pa_print(context, &uval, flag);
			} else {
				uval.uvaltype = PRA_UINT32;
				uval.uint32_val = value;
				if ((returnstat =
				    pa_print(context, &uval, flag)) != 0) {
					return (returnstat);
				}
			}
			break;
		case OTHER_OBJ:
			returnstat = pr_adr_u_int32(context, &value, 1);
			if (returnstat != 0)
				return (returnstat);

			if (!(context->format & PRF_RAWM)) {
				uval.uvaltype = PRA_STRING;
				uval.string_val = gettext("other");
				returnstat = pa_print(context, &uval, flag);
			} else {
				uval.uvaltype = PRA_UINT32;
				uval.uint32_val = value;
				if ((returnstat =
				    pa_print(context, &uval, flag)) != 0) {
					return (returnstat);
				}
			}
			break;
		default:
			returnstat = pr_adr_u_int32(context, &value, 1);
			if (returnstat != 0)
				return (returnstat);

			if (!(context->format & PRF_RAWM)) {
				uval.uvaltype = PRA_STRING;
				uval.string_val = gettext("unrecognized");
				returnstat = pa_print(context, &uval, flag);
			} else {
				uval.uvaltype = PRA_UINT32;
				uval.uint32_val = value;
				if ((returnstat =
				    pa_print(context, &uval, flag)) != 0) {
					return (returnstat);
				}
			}
	}

	if ((returnstat = close_tag(context, TAG_ACLVAL)) != 0)
		return (returnstat);

	return (returnstat);
}


/*
 * -----------------------------------------------------------------------
 * pa_event_modifier(): Issues pr_adr_u_short to retrieve the next ADR item from
 *		  the input stream pointed to by audit_adr.  This is the
 *		  event type, and is displayed in hex;
 * return codes : -1 - error
 *		:  0 - successful
 * -----------------------------------------------------------------------
 */
int
pa_event_modifier(pr_context_t *context, int status,  int flag)
{
	int	returnstat;
	au_emod_t emodifier;
	uval_t	uval;
	char	modstring[64];

	if (status < 0)
		return (status);

	if ((returnstat = pr_adr_u_short(context, &emodifier, 1)) != 0)
		return (returnstat);

	/* For XML, only print when modifier is non-zero */
	if (!(context->format & PRF_XMLM) || (emodifier != 0)) {
		uval.uvaltype = PRA_STRING;

		returnstat = open_tag(context, TAG_EVMOD);

		if (returnstat >= 0) {
			if (!(context->format & PRF_RAWM)) {
				eventmodifier2string(emodifier, modstring,
				    sizeof (modstring));
				uval.string_val = modstring;
				returnstat = pa_print(context, &uval, flag);
			} else {
				uval.string_val = hexconvert((char *)&emodifier,
				    sizeof (emodifier), sizeof (emodifier));
				if (uval.string_val) {
					returnstat = pa_print(context, &uval,
					    flag);
					free(uval.string_val);
				}
			}
		}
		if (returnstat >= 0)
			returnstat = close_tag(context, TAG_EVMOD);
	}

	return (returnstat);
}


/*
 * -----------------------------------------------------------------------
 * pa_event_type(): Issues pr_adr_u_short to retrieve the next ADR item from
 *		  the input stream pointed to by audit_adr.  This is the
 *		  event type, and is displayed in either raw or
 *		  ASCII form as appropriate
 * return codes : -1 - error
 *		:  0 - successful
 * -----------------------------------------------------------------------
 */
int
pa_event_type(pr_context_t *context, int status,  int flag)
{
	au_event_t etype;
	int	returnstat;
	au_event_ent_t *p_event = NULL;
	uval_t	uval;

	if (status >= 0) {
		if ((returnstat = pr_adr_u_short(context, &etype, 1)) == 0) {
			if (!(context->format & PRF_RAWM)) {
				uval.uvaltype = PRA_STRING;
				if (context->format & PRF_NOCACHE) {
					p_event = getauevnum(etype);
				} else {
					(void) cacheauevent(&p_event, etype);
				}
				if (p_event != NULL) {
					if (context->format & PRF_SHORTM)
						uval.string_val =
						    p_event->ae_name;
					else
						uval.string_val =
						    p_event->ae_desc;
				} else {
					uval.string_val =
					    gettext("invalid event number");
				}
				returnstat = pa_print(context, &uval, flag);
			} else {
				uval.uvaltype = PRA_USHORT;
				uval.ushort_val = etype;
				returnstat = pa_print(context, &uval, flag);
			}
		}
		return (returnstat);
	} else
		return (status);

}


/*
 * Print time from struct timeval to millisecond resolution.
 *
 *	typedef long	time_t;		time of day in seconds
 *	typedef	long	useconds_t;	signed # of microseconds
 *
 * struct timeval {
 *	time_t		tv_sec;		seconds
 *	suseconds_t	tv_usec;	and microseconds
 * };
 */

int
pa_utime32(pr_context_t *context, int status, int flag)
{
	uint32_t scale = 1000;		/* usec to msec */

	return (do_mtime32(context, status, flag, scale));
}

/*
 * Print time from timestruc_t to millisecond resolution.
 *
 *	typedef struct timespec timestruct_t;
 * struct timespec{
 *	time_t	tv_sec;		seconds
 *	long	tv_nsec;	and nanoseconds
 * };
 */
int
pa_ntime32(pr_context_t *context, int status, int flag)
{
	uint32_t scale = 1000000;	/* nsec to msec */

	return (do_mtime32(context, status, flag, scale));
}

/*
 * Format the timezone +/- HH:MM and terminate the string
 * Note tm and tv_sec are the same time.
 * Too bad strftime won't produce an ISO 8601 time zone numeric
 */

#define	MINS	(24L * 60)
static void
tzone(struct tm *tm, time_t *tv_sec, char *p)
{
	struct tm *gmt;
	int min_off;

	gmt = gmtime(tv_sec);

	min_off = ((tm->tm_hour - gmt->tm_hour) * 60) +
	    (tm->tm_min - gmt->tm_min);

	if (tm->tm_year < gmt->tm_year)		/* cross new year */
		min_off -= MINS;
	else if (tm->tm_year > gmt->tm_year)
		min_off += MINS;
	else if (tm->tm_yday < gmt->tm_yday)	/* cross dateline */
		min_off -= MINS;
	else if (tm->tm_yday > gmt->tm_yday)
		min_off += MINS;

	if (min_off < 0) {
		min_off = -min_off;
		*p++ = '-';
	} else {
		*p++ = '+';
	}

	*p++ = min_off / 600 + '0';		/* 10s of hours */
	min_off = min_off - min_off / 600 * 600;
	*p++ = min_off / 60 % 10 + '0';		/* hours */
	min_off = min_off - min_off / 60 * 60;
	*p++ = ':';
	*p++ = min_off / 10 + '0';		/* 10s of minutes */
	*p++ = min_off % 10 + '0';		/* minutes */
	*p = '\0';
}

/*
 * Format the milliseconds in place in the string.
 * Borrowed from strftime.c:itoa()
 */
static void
msec32(uint32_t msec, char *p)
{
	*p++ = msec / 100 + '0';
	msec  = msec - msec / 100 * 100;
	*p++ = msec / 10 + '0';
	*p++ = msec % 10 +'0';
}

/*
 * Format time and print relative to scale factor from micro/nano seconds.
 */
static int
do_mtime32(pr_context_t *context, int status, int flag, uint32_t scale)
{
	uint32_t t32;
	time_t tv_sec;
	struct tm tm;
	char	time_created[sizeof ("YYYY-MM-DD HH:MM:SS.sss -HH:MM")];
	int	returnstat;
	uval_t	uval;

	if (status < 0)
		return (status);

	if ((returnstat = open_tag(context, TAG_ISO)) != 0)
		return (returnstat);

	if ((returnstat = pr_adr_u_int32(context,
	    (uint32_t *)&tv_sec, 1)) != 0)
		return (returnstat);
	if ((returnstat = pr_adr_u_int32(context, &t32, 1)) == 0) {
		if (!(context->format & PRF_RAWM)) {
			(void) localtime_r(&tv_sec, &tm);
			(void) strftime(time_created,
			    sizeof ("YYYY-MM-DD HH:MM:SS.xxx "),
			    "%Y-%m-%d %H:%M:%S.xxx ", &tm);
			msec32(t32/scale,
			    &time_created[sizeof ("YYYY-MM-DD HH:MM:SS.")-1]);
			tzone(&tm, &tv_sec,
			    &time_created[
			    sizeof ("YYYY-MM-DD HH:MM:SS.xxx ")-1]);
			uval.uvaltype = PRA_STRING;
			uval.string_val = time_created;
		} else {
			uval.uvaltype = PRA_UINT32;
			uval.uint32_val = (uint32_t)tv_sec;
			(void) pa_print(context, &uval, 0);
			if (context->format & PRF_XMLM) {
				uval.uvaltype = PRA_CHAR;
				uval.char_val = '.';
				(void) pa_print(context, &uval, 0);
			}
			uval.uvaltype = PRA_UINT32;
			uval.uint32_val = t32;
		}
		returnstat = pa_print(context, &uval, flag);
	}

	if (returnstat == 0)
		return (close_tag(context, TAG_ISO));
	else
		return (returnstat);
}

/*
 * Print time from struct timeval to millisecond resolution.
 *
 *	typedef long	time_t;		time of day in seconds
 *	typedef	long	useconds_t;	signed # of microseconds
 *
 * struct timeval {
 *	time_t		tv_sec;		seconds
 *	suseconds_t	tv_usec;	and microseconds
 * };
 */

int
pa_utime64(pr_context_t *context, int status, int flag)
{
	uint64_t scale = 1000;		/* usec to msec */

	return (do_mtime64(context, status, flag, scale));
}

/*
 * Print time from timestruc_t to millisecond resolution.
 *
 *	typedef struct timespec timestruct_t;
 * struct timespec{
 *	time_t	tv_sec;		seconds
 *	long	tv_nsec;	and nanoseconds
 * };
 */
int
pa_ntime64(pr_context_t *context, int status, int flag)
{
	uint64_t scale = 1000000;	/* nsec to msec */

	return (do_mtime64(context, status, flag, scale));
}

/*
 * Format the milliseconds in place in the string.
 * Borrowed from strftime.c:itoa()
 */
static void
msec64(uint64_t msec, char *p)
{
	*p++ = msec / 100 + '0';
	msec = msec - msec / 100 * 100;
	*p++ = msec / 10 + '0';
	*p++ = msec % 10 +'0';
}

/*
 * Format time and print relative to scale factor from micro/nano seconds.
 */
static int
do_mtime64(pr_context_t *context, int status, int flag, uint64_t scale)
{
	uint64_t t64_sec;
	uint64_t t64_msec;
	time_t tv_sec;
	struct tm tm;
	char	time_created[sizeof ("YYYY-MM-DD HH:MM:SS.sss -HH:MM")];
	int	returnstat;
	uval_t	uval;

	if (status < 0)
		return (status);

	if ((returnstat = open_tag(context, TAG_ISO)) != 0)
		return (returnstat);

	if ((returnstat = pr_adr_u_int64(context, &t64_sec, 1)) != 0)
		return (returnstat);
	if ((returnstat = pr_adr_u_int64(context, &t64_msec, 1)) == 0) {
		if (!(context->format & PRF_RAWM)) {
#ifndef	_LP64
			/*
			 * N.B.
			 * This fails for years from 2038
			 * The Y2K+38 problem
			 */
#endif	/* !_LP64 */
			tv_sec = (time_t)t64_sec;
			(void) localtime_r(&tv_sec, &tm);
			(void) strftime(time_created,
			    sizeof ("YYYY-MM-DD HH:MM:SS.xxx "),
			    "%Y-%m-%d %H:%M:%S.xxx ", &tm);
			msec64(t64_msec/scale,
			    &time_created[sizeof ("YYYY-MM-DD HH:MM:SS.")-1]);
			tzone(&tm, &tv_sec,
			    &time_created[
			    sizeof ("YYYY-MM-DD HH:MM:SS.xxx ")-1]);
			uval.uvaltype = PRA_STRING;
			uval.string_val = time_created;
		} else {
			uval.uvaltype = PRA_UINT64;
			uval.uint64_val = t64_sec;
			(void) pa_print(context, &uval, 0);
			if (context->format & PRF_XMLM) {
				uval.uvaltype = PRA_CHAR;
				uval.char_val = '.';
				(void) pa_print(context, &uval, 0);
			}
			uval.uvaltype = PRA_UINT64;
			uval.uint64_val = t64_msec;
		}
		returnstat = pa_print(context, &uval, flag);
	}

	if (returnstat < 0)
		return (returnstat);

	return (close_tag(context, TAG_ISO));
}

/*
 * -----------------------------------------------------------------------
 * pa_error()   :  convert the return token error code.
 *
 * output	: buf string representing return token error code.
 *
 * -----------------------------------------------------------------------
 */
void
pa_error(const uchar_t err, char *buf, size_t buflen)
{
	if (err == ADT_SUCCESS) {
		(void) strlcpy(buf, gettext("success"), buflen);
	} else if ((char)err == ADT_FAILURE) {
		(void) strlcpy(buf, gettext("failure"), buflen);
	} else {
		char *emsg = strerror(err);

		if (emsg != NULL) {
			(void) strlcpy(buf, gettext("failure: "), buflen);
			(void) strlcat(buf, emsg, buflen);
		} else {
			(void) snprintf(buf, buflen, "%s%d",
			    gettext("failure: "), err);
		}
	}
}

/*
 * -----------------------------------------------------------------------
 * pa_retval()  :  convert the return token return value code.
 *
 * input	: err, for kernel success 0, or
 *			failure errno: 0 > & < sys_nerr.
 *			for userland success ADT_SUCCESS (0) or
 *			failure ADT_FAILURE (-1).
 *		pa_error() above has already converted err.
 *
 *		: retval, for kernel arbitrary return value for success, or
 *			failure: -1.
 *			for userland,
 *			>= ADT_FAIL_VALUE < ADT_FAIL_PAM, an adt message code;
 *			>= ADT_FAIL_PAM, a pam_strerror value;
 *			< ADT_FAIL_VALUE, supposed to be an errno.
 *
 * output	: buf string representing return token error code.
 *
 * -----------------------------------------------------------------------
 */
void
pa_retval(const uchar_t err, const int32_t retval, char *buf, size_t buflen)
{
	struct msg_text *msglist;
	char *emsg;

	/* success or kernel failure */
	if (((char)err == ADT_SUCCESS) ||
	    (retval < 0)) {

		(void) snprintf(buf, buflen, "%d", retval);
		return;
	}

	/* userland failure */
	msglist = &adt_msg_text[ADT_LIST_FAIL_VALUE];

	if ((retval + msglist->ml_offset >= msglist->ml_min_index) &&
	    (retval + msglist->ml_offset <= msglist->ml_max_index)) {

		(void) strlcpy(buf,
		    gettext(msglist->ml_msg_list[retval + msglist->ml_offset]),
		    buflen);
	} else if ((retval >= ADT_FAIL_PAM) &&
	    (retval < ADT_FAIL_PAM + PAM_TOTAL_ERRNUM)) {

		(void) strlcpy(buf, pam_strerror(NULL, retval - ADT_FAIL_PAM),
		    buflen);
	} else if ((emsg = strerror(retval)) != NULL) {

		(void) strlcpy(buf, emsg, buflen);
	} else {

		(void) snprintf(buf, buflen, "%d", retval);
	}
}

/*
 * -----------------------------------------------------------------------
 * pa_printstr()	:  print a given string, translating unprintables
 *			:  as needed.
 */
static int
pa_printstr(pr_context_t *context, char *str)
{
	int	err = 0;
	int	len, printable;
	int	mbmax = MB_CUR_MAX;
	wchar_t	wc;
	char	c;

	if (mbmax == 1) {
		/* fast path */
		while (err == 0 && *str != '\0') {
			c = *str++;
			printable = isprint((unsigned char)c);
			err = pa_putstr(context, printable, &c, 1);
		}
		return (err);
	}
	while (err == 0 && *str != '\0') {
		len = mbtowc(&wc, str, mbmax);
		if (len <= 0) {
			len = 1;
			printable = 0;
		} else {
			printable = iswprint(wc);
		}
		err = pa_putstr(context, printable, str, len);
		str += len;
	}
	return (err);
}

/*
 * -----------------------------------------------------------------------
 * pa_print()	:  print as one str or formatted for easy reading.
 * 		: flag - indicates whether to output a new line for
 *		: multi-line output.
 * 		:		= 0; no new line
 *		:		= 1; new line if regular output
 * output	: The audit record information is displayed in the
 *		  type specified by uvaltype and value specified in
 *		  uval.  The printing of the delimiter or newline is
 *		  determined by PRF_ONELINE, and the flag value,
 *		  as follows:
 *			+--------+------+------+-----------------+
 *			|ONELINE | flag | last | Action          |
 *			+--------+------+------+-----------------+
 *			|    Y   |   Y  |   T  | print new line  |
 *			|    Y   |   Y  |   F  | print delimiter |
 *			|    Y   |   N  |   T  | print new line  |
 *			|    Y   |   N  |   F  | print delimiter |
 *			|    N   |   Y  |   T  | print new line  |
 *			|    N   |   Y  |   F  | print new line  |
 *			|    N   |   N  |   T  | print new line  |
 *			|    N   |   N  |   F  | print delimiter |
 *			+--------+------+------+-----------------+
 *
 * return codes : -1 - error
 *		0 - successful
 * -----------------------------------------------------------------------
 */
int
pa_print(pr_context_t *context, uval_t *uval, int flag)
{
	int	returnstat = 0;
	int	last;

	switch (uval->uvaltype) {
	case PRA_INT32:
		returnstat = pr_printf(context, "%d", uval->int32_val);
		break;
	case PRA_UINT32:
		returnstat = pr_printf(context, "%u", uval->uint32_val);
		break;
	case PRA_INT64:
		returnstat = pr_printf(context, "%"PRId64, uval->int64_val);
		break;
	case PRA_UINT64:
		returnstat = pr_printf(context, "%"PRIu64, uval->uint64_val);
		break;
	case PRA_SHORT:
		returnstat = pr_printf(context, "%hd", uval->short_val);
		break;
	case PRA_USHORT:
		returnstat = pr_printf(context, "%hu", uval->ushort_val);
		break;
	case PRA_CHAR:
		returnstat = pr_printf(context, "%c", uval->char_val);
		break;
	case PRA_BYTE:
		returnstat = pr_printf(context, "%d", uval->char_val);
		break;
	case PRA_STRING:
		returnstat = pa_printstr(context, uval->string_val);
		break;
	case PRA_HEX32:
		returnstat = pr_printf(context, "0x%x", uval->int32_val);
		break;
	case PRA_HEX64:
		returnstat = pr_printf(context, "0x%"PRIx64, uval->int64_val);
		break;
	case PRA_SHEX:
		returnstat = pr_printf(context, "0x%hx", uval->short_val);
		break;
	case PRA_OCT:
		returnstat = pr_printf(context, "%ho", uval->ushort_val);
		break;
	case PRA_LOCT:
		returnstat = pr_printf(context, "%o", (int)uval->uint32_val);
		break;
	default:
		(void) fprintf(stderr, gettext("praudit: Unknown type.\n"));
		returnstat = -1;
		break;
	}
	if (returnstat < 0)
		return (returnstat);

	last = (context->audit_adr->adr_now ==
	    (context->audit_rec_start + context->audit_rec_len));

	if (!(context->format & PRF_XMLM)) {
		if (!(context->format & PRF_ONELINE)) {
			if ((flag == 1) || last)
				returnstat = pr_putchar(context, '\n');
			else
				returnstat = pr_printf(context, "%s",
				    context->SEPARATOR);
		} else {
			if (!last)
				returnstat = pr_printf(context, "%s",
				    context->SEPARATOR);
			else
				returnstat = pr_putchar(context, '\n');
		}
	}
	if ((returnstat == 0) && (context->data_mode == FILEMODE))
		(void) fflush(stdout);

	return (returnstat);
}

static struct cntrl_mapping {
	char from;
	char to;
} cntrl_map[] = {
	'\0', '0',
	'\a', 'a',
	'\b', 'b',
	'\t', 't',
	'\f', 'f',
	'\n', 'n',
	'\r', 'r',
	'\v', 'v'
};

static int cntrl_map_entries = sizeof (cntrl_map)
	/ sizeof (struct cntrl_mapping);

/*
 * Convert binary data to ASCII for printing.
 */
void
convertascii(char *p, char *c, int size)
{
	int	i, j, uc;

	for (i = 0; i < size; i++) {
		uc = (unsigned char)*(c + i);
		if (isascii(uc)) {
			if (iscntrl(uc)) {
				for (j = 0; j < cntrl_map_entries; j++) {
					if (cntrl_map[j].from == uc) {
						*p++ = '\\';
						*p++ = cntrl_map[j].to;
						break;
					}
				}
				if (j == cntrl_map_entries) {
					*p++ = '^';
					*p++ = (char)(uc ^ 0100);
				}
			} else {
				*p++ = (char)uc;
			}
		} else {
			p += sprintf(p, "\\%03o", uc);
		}
	}
	*p = '\0';
}

/*
 * -----------------------------------------------------------------------
 * pa_xgeneric: Process Xobject token and display contents
 *		      This routine will handle many of the attribute
 *		      types introduced in TS 2.x, such as:
 *
 *		      AUT_XCOLORMAP, AUT_XCURSOR, AUT_XFONT,
 *		      AUT_XGC, AUT_XPIXMAP, AUT_XWINDOW
 *
 * NOTE: At the time of call, the token id has been retrieved
 *
 * return codes		: -1 - error
 *			:  0 - successful
 * NOTE: At the time of call, the xatom token id has been retrieved
 *
 * Format of xobj
 *	text token id		adr_char
 * 	XID 			adr_u_int32
 * 	creator uid		adr_pw_uid
 * -----------------------------------------------------------------------
 */
int
pa_xgeneric(pr_context_t *context)
{
	int	returnstat;

	returnstat = process_tag(context, TAG_XID, 0, 0);
	return (process_tag(context, TAG_XCUID, returnstat, 1));
}


/*
 * ------------------------------------------------------------------------
 * pa_liaison : Issues pr_adr_char to retrieve the next ADR item from the
 *			input stream pointed to by audit_adr, and prints it
 *			if status >= 0 either in ASCII or raw form
 * return codes : -1 - error
 *		: 0 - successful
 *		: 1 - warning, unknown label type
 * -----------------------------------------------------------------------
 */
int
pa_liaison(pr_context_t *context, int status, int flag)
{
	int	returnstat;
	int32_t	li;
	uval_t	uval;

	if (status >= 0) {
		if ((returnstat = pr_adr_int32(context, &li, 1)) != 0) {
			return (returnstat);
		}
		if (!(context->format & PRF_RAWM)) {
			uval.uvaltype = PRA_UINT32;
			uval.uint32_val = li;
			returnstat = pa_print(context, &uval, flag);
		}
		/* print in hexadecimal form */
		if ((context->format & PRF_RAWM) || (returnstat == 1)) {
			uval.uvaltype = PRA_HEX32;
			uval.uint32_val = li;
			returnstat = pa_print(context, &uval, flag);
		}
		return (returnstat);
	} else
		return (status);
}

/*
 * ------------------------------------------------------------------------
 * pa_xid : Issues pr_adr_int32 to retrieve the XID from the input
 *	      stream pointed to by audit_adr, and prints it if
 *	      status >= 0 either in ASCII or raw form
 * return codes : -1 - error
 *		:  0 - successful
 *		:  1 - warning, unknown label type
 * ------------------------------------------------------------------------
 */

int
pa_xid(pr_context_t *context, int status, int flag)
{
	int returnstat;
	int32_t xid;
	uval_t	uval;

	if (status < 0)
		return (status);

	/* get XID from stream */
	if ((returnstat = pr_adr_int32(context, (int32_t *)&xid, 1)) != 0)
		return (returnstat);

	if (!(context->format & PRF_RAWM)) {
		uval.uvaltype = PRA_STRING;
		uval.string_val = hexconvert((char *)&xid, sizeof (xid),
		    sizeof (xid));
		if (uval.string_val) {
			returnstat = pa_print(context, &uval, flag);
			free(uval.string_val);
		}
	} else {
		uval.uvaltype = PRA_INT32;
		uval.int32_val = xid;
		returnstat = pa_print(context, &uval, flag);
	}

	return (returnstat);
}

static int
pa_ace_flags(pr_context_t *context, ace_t *ace, int status, int flag)
{
	int	returnstat;
	uval_t	uval;

	if (status < 0)
		return (status);

	/*
	 * TRANSLATION_NOTE
	 * ace->a_flags refers to access flags of ZFS/NFSv4 ACL entry.
	 */
	if ((returnstat = open_tag(context, TAG_ACEFLAGS)) != 0)
		return (returnstat);
	if (!(context->format & PRF_RAWM)) {
		uval.uvaltype = PRA_STRING;
		switch (ace->a_flags & ACE_TYPE_FLAGS) {
		case ACE_OWNER:
			uval.string_val = gettext(OWNERAT_TXT);
			break;
		case ACE_GROUP | ACE_IDENTIFIER_GROUP:
			uval.string_val = gettext(GROUPAT_TXT);
			break;
		case ACE_IDENTIFIER_GROUP:
			uval.string_val = gettext(GROUP_TXT);
			break;
		case ACE_EVERYONE:
			uval.string_val = gettext(EVERYONEAT_TXT);
			break;
		case 0:
			uval.string_val = gettext(USER_TXT);
			break;
		default:
			uval.uvaltype = PRA_USHORT;
			uval.uint32_val = ace->a_flags;
		}
	} else {
		uval.uvaltype = PRA_USHORT;
		uval.uint32_val = ace->a_flags;
	}
	if ((returnstat = pa_print(context, &uval, flag)) != 0)
		return (returnstat);
	return (close_tag(context, TAG_ACEFLAGS));
}

static int
pa_ace_who(pr_context_t *context, ace_t *ace, int status, int flag)
{
	int		returnstat;

	if (status < 0)
		return (status);

	/*
	 * TRANSLATION_NOTE
	 * ace->a_who refers to user id or group id of ZFS/NFSv4 ACL entry.
	 */
	if ((returnstat = open_tag(context, TAG_ACEID)) != 0)
		return (returnstat);
	switch (ace->a_flags & ACE_TYPE_FLAGS) {
	case ACE_IDENTIFIER_GROUP:	/* group id */
		returnstat = pa_print_gid(context, ace->a_who, returnstat,
		    flag);
		break;
	default:			/* user id */
		returnstat = pa_print_uid(context, ace->a_who, returnstat,
		    flag);
		break;
	}
	if (returnstat < 0)
		return (returnstat);
	return (close_tag(context, TAG_ACEID));
}

/*
 * Appends what to str, (re)allocating str if necessary.
 */
#define	INITIAL_ALLOC	256
static int
strappend(char **str, char *what, size_t *alloc)
{
	char	*s, *newstr;
	size_t	needed;

	s = *str;

	if (s == NULL) {
		s = malloc(INITIAL_ALLOC);
		if (s == NULL) {
			*alloc = 0;
			return (-1);
		}
		*alloc = INITIAL_ALLOC;
		s[0] = '\0';
		*str = s;
	}

	needed = strlen(s) + strlen(what) + 1;
	if (*alloc < needed) {
		newstr = realloc(s, needed);
		if (newstr == NULL)
			return (-1);
		s = newstr;
		*alloc = needed;
		*str = s;
	}
	(void) strlcat(s, what, *alloc);

	return (0);
}

static int
pa_ace_access_mask(pr_context_t *context, ace_t *ace, int status, int flag)
{
	int	returnstat, i;
	uval_t	uval;
	char	*permstr = NULL;
	size_t	permstr_alloc = 0;

	if (status < 0)
		return (status);

	/*
	 * TRANSLATION_NOTE
	 * ace->a_access_mask refers to access mask of ZFS/NFSv4 ACL entry.
	 */
	if ((returnstat = open_tag(context, TAG_ACEMASK)) != 0)
		return (returnstat);
	if (context->format & PRF_SHORTM &&
	    ((permstr = malloc(15)) != NULL)) {
		for (i = 0; i < 14; i++)
			permstr[i] = '-';

		if (ace->a_access_mask & ACE_READ_DATA)
			permstr[0] = 'r';
		if (ace->a_access_mask & ACE_WRITE_DATA)
			permstr[1] = 'w';
		if (ace->a_access_mask & ACE_EXECUTE)
			permstr[2] = 'x';
		if (ace->a_access_mask & ACE_APPEND_DATA)
			permstr[3] = 'p';
		if (ace->a_access_mask & ACE_DELETE)
			permstr[4] = 'd';
		if (ace->a_access_mask & ACE_DELETE_CHILD)
			permstr[5] = 'D';
		if (ace->a_access_mask & ACE_READ_ATTRIBUTES)
			permstr[6] = 'a';
		if (ace->a_access_mask & ACE_WRITE_ATTRIBUTES)
			permstr[7] = 'A';
		if (ace->a_access_mask & ACE_READ_NAMED_ATTRS)
			permstr[8] = 'R';
		if (ace->a_access_mask & ACE_WRITE_NAMED_ATTRS)
			permstr[9] = 'W';
		if (ace->a_access_mask & ACE_READ_ACL)
			permstr[10] = 'c';
		if (ace->a_access_mask & ACE_WRITE_ACL)
			permstr[11] = 'C';
		if (ace->a_access_mask & ACE_WRITE_OWNER)
			permstr[12] = 'o';
		if (ace->a_access_mask & ACE_SYNCHRONIZE)
			permstr[13] = 's';
		permstr[14] = '\0';
		uval.uvaltype = PRA_STRING;
		uval.string_val = permstr;
	} else if (!(context->format & PRF_RAWM)) {

		/*
		 * Note this differs from acltext.c:ace_perm_txt()
		 * because we don't know if the acl belongs to a file
		 * or directory. ace mask value are the same
		 * nonetheless, see sys/acl.h
		 */
		if (ace->a_access_mask & ACE_LIST_DIRECTORY) {
			returnstat = strappend(&permstr, gettext(READ_DIR_TXT),
			    &permstr_alloc);
		}
		if (ace->a_access_mask & ACE_ADD_FILE) {
			returnstat = strappend(&permstr, gettext(ADD_FILE_TXT),
			    &permstr_alloc);
		}
		if (ace->a_access_mask & ACE_ADD_SUBDIRECTORY) {
			returnstat = strappend(&permstr, gettext(ADD_DIR_TXT),
			    &permstr_alloc);
		}
		if (ace->a_access_mask & ACE_READ_NAMED_ATTRS) {
			returnstat = strappend(&permstr,
			    gettext(READ_XATTR_TXT), &permstr_alloc);
		}
		if (ace->a_access_mask & ACE_WRITE_NAMED_ATTRS) {
			returnstat = strappend(&permstr,
			    gettext(WRITE_XATTR_TXT), &permstr_alloc);
		}
		if (ace->a_access_mask & ACE_EXECUTE) {
			returnstat = strappend(&permstr,
			    gettext(EXECUTE_TXT), &permstr_alloc);
		}
		if (ace->a_access_mask & ACE_DELETE_CHILD) {
			returnstat = strappend(&permstr,
			    gettext(DELETE_CHILD_TXT), &permstr_alloc);
		}
		if (ace->a_access_mask & ACE_READ_ATTRIBUTES) {
			returnstat = strappend(&permstr,
			    gettext(READ_ATTRIBUTES_TXT), &permstr_alloc);
		}
		if (ace->a_access_mask & ACE_WRITE_ATTRIBUTES) {
			returnstat = strappend(&permstr,
			    gettext(WRITE_ATTRIBUTES_TXT), &permstr_alloc);
		}
		if (ace->a_access_mask & ACE_DELETE) {
			returnstat = strappend(&permstr, gettext(DELETE_TXT),
			    &permstr_alloc);
		}
		if (ace->a_access_mask & ACE_READ_ACL) {
			returnstat = strappend(&permstr, gettext(READ_ACL_TXT),
			    &permstr_alloc);
		}
		if (ace->a_access_mask & ACE_WRITE_ACL) {
			returnstat = strappend(&permstr, gettext(WRITE_ACL_TXT),
			    &permstr_alloc);
		}
		if (ace->a_access_mask & ACE_WRITE_OWNER) {
			returnstat = strappend(&permstr,
			    gettext(WRITE_OWNER_TXT), &permstr_alloc);
		}
		if (ace->a_access_mask & ACE_SYNCHRONIZE) {
			returnstat = strappend(&permstr,
			    gettext(SYNCHRONIZE_TXT), &permstr_alloc);
		}
		if (permstr[strlen(permstr) - 1] == '/')
			permstr[strlen(permstr) - 1] = '\0';
		uval.uvaltype = PRA_STRING;
		uval.string_val = permstr;
	}
	if ((permstr == NULL) || (returnstat != 0) ||
	    (context->format & PRF_RAWM)) {
		uval.uvaltype = PRA_UINT32;
		uval.uint32_val = ace->a_access_mask;
	}
	returnstat = pa_print(context, &uval, flag);

	if (permstr != NULL)
		free(permstr);
	if (returnstat != 0)
		return (returnstat);
	return (close_tag(context, TAG_ACEMASK));
}

static int
pa_ace_type(pr_context_t *context, ace_t *ace, int status, int flag)
{
	int	returnstat;
	uval_t	uval;

	if (status < 0)
		return (status);

	/*
	 * TRANSLATION_NOTE
	 * ace->a_type refers to access type of ZFS/NFSv4 ACL entry.
	 */
	if ((returnstat = open_tag(context, TAG_ACETYPE)) != 0)
		return (returnstat);
	if (!(context->format & PRF_RAWM)) {
		uval.uvaltype = PRA_STRING;
		switch (ace->a_type) {
		case ACE_ACCESS_ALLOWED_ACE_TYPE:
			uval.string_val = gettext(ALLOW_TXT);
			break;
		case ACE_ACCESS_DENIED_ACE_TYPE:
			uval.string_val = gettext(DENY_TXT);
			break;
		case ACE_SYSTEM_AUDIT_ACE_TYPE:
			uval.string_val = gettext(AUDIT_TXT);
			break;
		case ACE_SYSTEM_ALARM_ACE_TYPE:
			uval.string_val = gettext(ALARM_TXT);
			break;
		default:
			uval.string_val = gettext(UNKNOWN_TXT);
		}
	} else {
		uval.uvaltype = PRA_USHORT;
		uval.uint32_val = ace->a_type;
	}
	if ((returnstat = pa_print(context, &uval, flag)) != 0)
		return (returnstat);
	return (close_tag(context, TAG_ACETYPE));
}

int
pa_ace(pr_context_t *context, int status, int flag)
{
	int		returnstat;
	ace_t		ace;

	if (status < 0)
		return (status);

	if ((returnstat = pr_adr_u_int32(context, &ace.a_who, 1)) != 0)
		return (returnstat);
	if ((returnstat = pr_adr_u_int32(context, &ace.a_access_mask, 1)) != 0)
		return (returnstat);
	if ((returnstat = pr_adr_u_short(context, &ace.a_flags, 1)) != 0)
		return (returnstat);
	if ((returnstat = pr_adr_u_short(context, &ace.a_type, 1)) != 0)
		return (returnstat);

	if ((returnstat = pa_ace_flags(context, &ace, returnstat, 0)) != 0)
		return (returnstat);
	/* pa_ace_who can returns 1 if uid/gid is not found */
	if ((returnstat = pa_ace_who(context, &ace, returnstat, 0)) < 0)
		return (returnstat);
	if ((returnstat = pa_ace_access_mask(context, &ace,
	    returnstat, 0)) != 0)
		return (returnstat);
	return (pa_ace_type(context, &ace, returnstat, flag));
}