Blob Blame History Raw
/* audit_logging.c -- 
 * Copyright 2005-2008,2010,2011,2013,2017 Red Hat Inc., Durham, North Carolina.
 * All Rights Reserved.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 * Authors:
 *      Steve Grubb <sgrubb@redhat.com>
 */

#include "config.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <errno.h>
#include <netinet/in.h> // inet6 addrlen
#include <netdb.h>	// gethostbyname
#include <arpa/inet.h>	// inet_ntop
#include <utmp.h>
#include <limits.h>	// PATH_MAX
#include <fcntl.h>

#include "libaudit.h"
#include "private.h"

#define TTY_PATH	32
#define MAX_USER	(UT_NAMESIZE * 2) + 8

// NOTE: The kernel fills in pid, uid, and loginuid of sender. Therefore,
// these routines do not need to send them.

/*
 * resolve's the hostname - caller must pass a INET6_ADDRSTRLEN byte buffer
 * Returns string w/ numerical address, or "?" on failure
 */
static void _resolve_addr(char buf[], const char *host)
{
	struct addrinfo *ai;
	struct addrinfo hints;
	int e;

	buf[0] = '?';
	buf[1] = 0;
	/* Short circuit this lookup if NULL, or empty */
	if (host == NULL || *host == 0)
		return;

	memset(&hints, 0, sizeof(hints));
	hints.ai_flags = AI_ADDRCONFIG;
	hints.ai_socktype = SOCK_STREAM;

	e = getaddrinfo(host, NULL, &hints, &ai);
	if (e != 0) {
		audit_msg(LOG_ERR, 
			"resolve_addr: cannot resolve hostname %s (%s)",
			host, gai_strerror(e));
		return;
	}
	// What to do if more than 1 addr?
	inet_ntop(ai->ai_family, ai->ai_family == AF_INET ?
		(void *) &((struct sockaddr_in *)ai->ai_addr)->sin_addr :
		(void *) &((struct sockaddr_in6 *)ai->ai_addr)->sin6_addr,
		buf, INET6_ADDRSTRLEN);
	freeaddrinfo(ai);
}

/*
 * This function checks a string to see if it needs encoding. It
 * return 1 if needed and 0 if not
 */
int audit_value_needs_encoding(const char *str, unsigned int size)
{
	unsigned int i;

	if (str == NULL)
		return 0;

	for (i=0; i<size; i++) {
		// we don't test for > 0x7f because str[] is signed.
		if (str[i] == '"' || str[i] < 0x21 || str[i] == 0x7F)
			return 1;
	}
	return 0;
}

/*
 * This function does encoding of "untrusted" names just like the kernel
 */
char *audit_encode_value(char *final, const char *buf, unsigned int size)
{
	unsigned int i;
	char *ptr = final;
	const char *hex = "0123456789ABCDEF";

	if (final == NULL)
		return NULL;

	if (buf == NULL) {
		*final = 0;
		return final;
	}

	for (i=0; i<size; i++) {
		*ptr++ = hex[(buf[i] & 0xF0)>>4]; /* Upper nibble */
		*ptr++ = hex[buf[i] & 0x0F];      /* Lower nibble */
	}
	*ptr = 0;
	return final;
}

char *audit_encode_nv_string(const char *name, const char *value,
		unsigned int vlen)
{
	char *str;

	if (vlen == 0 && value)
		vlen = strlen(value);

	if (value && audit_value_needs_encoding(value, vlen)) {
		char *tmp = malloc(2*vlen + 1);
		if (tmp) {
			audit_encode_value(tmp, value, vlen);
			if (asprintf(&str, "%s=%s", name, tmp) < 0)
				str = NULL;
			free(tmp);
		} else
			str = NULL;
	} else
		if (asprintf(&str, "%s=\"%s\"", name, value ? value : "?") < 0)
			str = NULL;
	return str;
}

/*
 * Get the executable's name 
 */
static char *_get_exename(char *exename, int size)
{
	int res;
	char tmp[PATH_MAX+1];

	/* get the name of the current executable */
	if ((res = readlink("/proc/self/exe", tmp, PATH_MAX)) == -1) {
		strcpy(exename, "\"?\"");
		audit_msg(LOG_ERR, "get_exename: cannot determine executable");
	} else {
		tmp[res] = '\0';
		if (audit_value_needs_encoding(tmp, res))
			return audit_encode_value(exename, tmp, res);
		snprintf(exename, size, "\"%s\"", tmp);
	}
	return exename;
}

/*
 * Get the command line name 
 * NOTE: at the moment, this only escapes what the user sent
 */
static char *_get_commname(const char *comm, char *commname, unsigned int size)
{
	unsigned int len;
	char tmp_comm[20];
	
	if (comm == NULL) {
		int len;
		int fd = open("/proc/self/comm", O_RDONLY);
		if (fd < 0) {
			strcpy(commname, "\"?\"");
			return commname;
		}
		len = read(fd, tmp_comm, sizeof(tmp_comm));
		close(fd);
		if (len > 0)
			tmp_comm[len-1] = 0;
		else {
			strcpy(commname, "\"?\"");
			return commname;
		}
		comm = tmp_comm;
	}

	len = strlen(comm);
	if (audit_value_needs_encoding(comm, len))
		audit_encode_value(commname, comm, len);
	else
		snprintf(commname, size, "\"%s\"", comm);

	return commname;
}

static int check_ttyname(const char *ttyn)
{
	struct stat statbuf;

	if (lstat(ttyn, &statbuf)
		|| !S_ISCHR(statbuf.st_mode)
		|| (statbuf.st_nlink > 1 && strncmp(ttyn, "/dev/", 5))) {
		audit_msg(LOG_ERR, "FATAL: bad tty %s", ttyn);
		return 1;
        }
	return 0;
}

static const char *_get_tty(char *tname, int size)
{
	int rc, i, found = 0;

	for (i=0; i<3 && !found; i++) {
		rc = ttyname_r(i, tname, size);
		if (rc == 0 && tname[0] != '\0')
			found = 1;
	}

	if (!found)
		return NULL;
	
	if (check_ttyname(tname)) 
		return NULL;

	if (strncmp(tname, "/dev/", 5) == 0)
		return &tname[5];

	return tname;
}

#define HOSTLEN 64
static char _host[HOSTLEN] = "";
static const char *_get_hostname(const char *ttyn)
{
	if (ttyn && ((strncmp(ttyn, "pts", 3) == 0) ||
		(strncmp(ttyn, "tty", 3) == 0) ||
		(strncmp(ttyn, "/dev/tty", 8) == 0) )) {
		if (_host[0] == 0) {
			gethostname(_host, HOSTLEN);
			_host[HOSTLEN - 1] = 0;
		}
		return _host;
	}
	return NULL;
}

/*
 * This function will log a message to the audit system using a predefined
 * message format. This function should be used by all console apps that do
 * not manipulate accounts or groups.
 *
 * audit_fd - The fd returned by audit_open
 * type - type of message, ex: AUDIT_USER, AUDIT_USYS_CONFIG, AUDIT_USER_LOGIN
 * message - the message being sent
 * hostname - the hostname if known
 * addr - The network address of the user
 * tty - The tty of the user
 * result - 1 is "success" and 0 is "failed"
 *
 * It returns the sequence number which is > 0 on success or <= 0 on error.
 */
int audit_log_user_message(int audit_fd, int type, const char *message,
	const char *hostname, const char *addr, const char *tty, int result)
{
	char buf[MAX_AUDIT_MESSAGE_LENGTH];
	char addrbuf[INET6_ADDRSTRLEN];
	static char exename[PATH_MAX*2]="";
	char ttyname[TTY_PATH];
	const char *success;
	int ret;

	if (audit_fd < 0)
		return 0;

	if (result)
		success = "success";
	else
		success = "failed";

	/* If hostname is empty string, make it NULL ptr */
	if (hostname && *hostname == 0)
		hostname = NULL;

	/* See if we can deduce addr */
	addrbuf[0] = 0;
	if (addr == NULL || strlen(addr) == 0)
		_resolve_addr(addrbuf, hostname);
	else
		strncat(addrbuf, addr, sizeof(addrbuf)-1);

	/* Fill in exec name if needed */
	if (exename[0] == 0)
		_get_exename(exename, sizeof(exename));

	/* Fill in tty if needed */
	if (tty == NULL) 
		tty = _get_tty(ttyname, TTY_PATH);
	else if (*tty == 0)
		tty = NULL;

	/* Get the local name if we have a real tty */
	if (hostname == NULL && tty)
		hostname = _get_hostname(tty);

	snprintf(buf, sizeof(buf),
		"%s exe=%s hostname=%s addr=%s terminal=%s res=%s",
		message, exename,
		hostname ? hostname : "?",
		addrbuf,
		tty ? tty : "?",
		success
		);

	errno = 0;
	ret = audit_send_user_message( audit_fd, type, HIDE_IT, buf );
	if ((ret < 1) && errno == 0)
		errno = ret;
	return ret;
}

/*
 * This function will log a message to the audit system using a predefined
 * message format. This function should be used by all console apps that do
 * not manipulate accounts or groups and are executing a script. An example
 * would be python or crond wanting to say what they are executing.
 *
 * audit_fd - The fd returned by audit_open
 * type - type of message, ex: AUDIT_USER, AUDIT_USYS_CONFIG, AUDIT_USER_LOGIN
 * message - the message being sent
 * comm - the program command line name
 * hostname - the hostname if known
 * addr - The network address of the user
 * tty - The tty of the user
 * result - 1 is "success" and 0 is "failed"
 *
 * It returns the sequence number which is > 0 on success or <= 0 on error.
 */
int audit_log_user_comm_message(int audit_fd, int type, const char *message,
	const char *comm, const char *hostname, const char *addr,
	const char *tty, int result)
{
	char buf[MAX_AUDIT_MESSAGE_LENGTH];
	char addrbuf[INET6_ADDRSTRLEN];
	static char exename[PATH_MAX*2]="";
	char commname[PATH_MAX*2];
	char ttyname[TTY_PATH];
	const char *success;
	int ret;

	if (audit_fd < 0)
		return 0;

	if (result)
		success = "success";
	else
		success = "failed";

	/* If hostname is empty string, make it NULL ptr */
	if (hostname && *hostname == 0)
		hostname = NULL;

	/* See if we can deduce addr */
	addrbuf[0] = 0;
	if (addr == NULL || strlen(addr) == 0)
		_resolve_addr(addrbuf, hostname);
	else
		strncat(addrbuf, addr, sizeof(addrbuf)-1);

	/* Fill in exec name if needed */
	if (exename[0] == 0)
		_get_exename(exename, sizeof(exename));

	/* Fill in tty if needed */
	if (tty == NULL) 
		tty = _get_tty(ttyname, TTY_PATH);
	else if (*tty == 0)
		tty = NULL;

	_get_commname(comm, commname, sizeof(commname));

	/* Get the local name if we have a real tty */
	if (hostname == NULL && tty)
		hostname = _get_hostname(tty);

	snprintf(buf, sizeof(buf),
		"%s comm=%s exe=%s hostname=%s addr=%s terminal=%s res=%s",
		message, commname, exename,
		hostname ? hostname : "?",
		addrbuf,
		tty ? tty : "?",
		success
		);

	errno = 0;
	ret = audit_send_user_message( audit_fd, type, HIDE_IT, buf );
	if ((ret < 1) && errno == 0)
		errno = ret;
	return ret;
}


/*
 * This function will log a message to the audit system using a predefined
 * message format. It should be used for all account manipulation operations.
 * Parameter usage is as follows:
 *
 * audit_fd - The fd returned by audit_open
 * type - type of message: AUDIT_USER_CHAUTHTOK for changing any account
 *        attributes.
 * pgname - program's name
 * op  -  operation. "adding user", "changing finger info", "deleting group"
 * name - user's account or group name. If not available use NULL.
 * id  -  uid or gid that the operation is being performed on. This is used
 *        only when user is NULL.
 * host - The hostname if known
 * addr - The network address of the user
 * tty  - The tty of the user
 * result - 1 is "success" and 0 is "failed"
 *
 * It returns the sequence number which is > 0 on success or <= 0 on error.
 */
int audit_log_acct_message(int audit_fd, int type, const char *pgname,
	const char *op, const char *name, unsigned int id, 
	const char *host, const char *addr, const char *tty, int result)
{
	const char *success;
	char buf[MAX_AUDIT_MESSAGE_LENGTH];
	char addrbuf[INET6_ADDRSTRLEN];
	static char exename[PATH_MAX*2] = "";
	char ttyname[TTY_PATH];
	int ret;

	if (audit_fd < 0)
		return 0;

	if (result)
		success = "success";
	else
		success = "failed";

	/* If hostname is empty string, make it NULL ptr */
	if (host && *host == 0)
		host = NULL;

	/* See if we can deduce addr */
	addrbuf[0] = 0;
	if (addr == NULL || strlen(addr) == 0)
		_resolve_addr(addrbuf, host);
	else
		strncat(addrbuf, addr, sizeof(addrbuf)-1);

	/* Fill in exec name if needed */
        if (pgname == NULL) {
		if (exename[0] == 0)
	                _get_exename(exename, sizeof(exename));
        } else if (pgname[0] != '"')
                snprintf(exename, sizeof(exename), "\"%s\"", pgname);
        else
                snprintf(exename, sizeof(exename), "%s", pgname);

	/* Fill in tty if needed */
	if (tty == NULL) 
		tty = _get_tty(ttyname, TTY_PATH);
	else if (*tty == 0)
		tty = NULL;

	/* Get the local name if we have a real tty */
	if (host == NULL && tty)
		host = _get_hostname(tty);

	if (name && id == -1) {
		char user[MAX_USER];
		const char *format;
		size_t len;

		user[0] = 0;
		strncat(user, name, MAX_USER-1);
		len = strnlen(user, UT_NAMESIZE);
		user[len] = 0;
		if (audit_value_needs_encoding(name, len)) {
			audit_encode_value(user, name, len);
			format = 
	     "op=%s acct=%s exe=%s hostname=%s addr=%s terminal=%s res=%s";
		} else
			format = 
	 "op=%s acct=\"%s\" exe=%s hostname=%s addr=%s terminal=%s res=%s";

		snprintf(buf, sizeof(buf), format,
			op, user, exename,
			host ? host : "?",
			addrbuf,
			tty ? tty : "?",
			success
			);
	} else
		snprintf(buf, sizeof(buf),
		"op=%s id=%u exe=%s hostname=%s addr=%s terminal=%s res=%s",
			op, id, exename,
			host ? host : "?",
			addrbuf,
			tty ? tty : "?",
			success
			);

	errno = 0;
	ret = audit_send_user_message(audit_fd, type, REAL_ERR, buf);
	if ((ret < 1) && errno == 0)
		errno = ret;
	return ret;
}

/*
 * This function will log a message to the audit system using a predefined
 * message format. This function should be used by all apps that are SE Linux
 * object managers.
 *
 * audit_fd - The fd returned by audit_open
 * type - type of message, ex: AUDIT_USER, AUDIT_USYS_CONFIG, AUDIT_USER_LOGIN
 * message - the message being sent
 * hostname - the hostname if known
 * addr - The network address of the user
 * tty - The tty of the user
 * uid - The auid of the person related to the avc message
 *
 * It returns the sequence number which is > 0 on success or <= 0 on error.
 */
int audit_log_user_avc_message(int audit_fd, int type, const char *message,
	const char *hostname, const char *addr, const char *tty, uid_t uid)
{
	char buf[MAX_AUDIT_MESSAGE_LENGTH];
	char addrbuf[INET6_ADDRSTRLEN];
	static char exename[PATH_MAX*2] = "";
	char ttyname[TTY_PATH];
	int retval;

	if (audit_fd < 0)
		return 0;

	/* If hostname is empty string, make it NULL ptr */
	if (hostname && *hostname == 0)
		hostname = NULL;

	addrbuf[0] = 0;
	if (addr == NULL || strlen(addr) == 0)
		_resolve_addr(addrbuf, hostname);
	else
		strncat(addrbuf, addr, sizeof(addrbuf)-1);

	if (exename[0] == 0)
		_get_exename(exename, sizeof(exename));

	if (tty == NULL) 
		tty = _get_tty(ttyname, TTY_PATH);
	else if (*tty == 0)
		tty = NULL;

	snprintf(buf, sizeof(buf),
	    "%s exe=%s sauid=%d hostname=%s addr=%s terminal=%s",
		message, exename, uid,
		hostname ? hostname : "?",
		addrbuf,
		tty ? tty : "?"
		);

	errno = 0;
	retval = audit_send_user_message( audit_fd, type, REAL_ERR, buf );
	if (retval == -EPERM && !audit_can_write()) {
		syslog(LOG_ERR, "Can't send to audit system: %s %s",
			audit_msg_type_to_name(type), buf);
		return 0;
	}
	if ((retval < 1) && errno == 0)
		errno = retval;
	return retval;
}

/*
 * This function will log a message to the audit system using a predefined
 * message format. It should be used for all SE linux user and role 
 * manipulation operations.
 * Parameter usage is as follows:
 *
 * type - type of message: AUDIT_ROLE_ASSIGN/REMOVE for changing any SE Linux
 *        user or role attributes.
 * pgname - program's name
 * op  -  operation. "adding-user", "adding-role", "deleting-user", "deleting-role"
 * name - user's account. If not available use NULL.
 * id  -  uid that the operation is being performed on. This is used
 *        only when name is NULL.
 * new_seuser - the new seuser that the login user is getting
 * new_role - the new_role that the login user is getting
 * new_range - the new mls range that the login user is getting
 * old_seuser - the old seuser that the login usr had
 * old_role - the old role that the login user had
 * old_range - the old mls range that the login usr had
 * host - The hostname if known
 * addr - The network address of the user
 * tty  - The tty of the user
 * result - 1 is "success" and 0 is "failed"
 *
 * It returns the sequence number which is > 0 on success or <= 0 on error.
 */
int audit_log_semanage_message(int audit_fd, int type, const char *pgname,
	const char *op, const char *name, unsigned int id, 
	const char *new_seuser, const char *new_role, const char *new_range,
	const char *old_seuser, const char *old_role, const char *old_range,
	const char *host, const char *addr,
	const char *tty, int result)
{
	const char *success;
	char buf[MAX_AUDIT_MESSAGE_LENGTH];
	char addrbuf[INET6_ADDRSTRLEN];
	static char exename[PATH_MAX*2] = "";
	char ttyname[TTY_PATH];
	int ret;

	if (audit_fd < 0)
		return 0;

	if (result)
		success = "success";
	else
		success = "failed";

	/* If hostname is empty string, make it NULL ptr */
	if (host && *host == 0)
		host = NULL;
	addrbuf[0] = 0;
	if (addr == NULL || strlen(addr) == 0)
		_resolve_addr(addrbuf, host);
	else
		strncat(addrbuf, addr, sizeof(addrbuf)-1);

	if (pgname == NULL || strlen(pgname) == 0) {
		if (exename[0] == 0)
			_get_exename(exename, sizeof(exename));
		pgname = exename;
	}

	if (tty == NULL || strlen(tty) == 0) 
		tty = _get_tty(ttyname, TTY_PATH);
	else if (*tty == 0)
		tty = NULL;

	if (name && strlen(name) > 0) {
		size_t len;
		const char *format;
		char user[MAX_USER];

		user[0] = 0;
		strncat(user, name, MAX_USER-1);
		len = strnlen(user, UT_NAMESIZE);
		user[len] = 0;
		if (audit_value_needs_encoding(name, len)) {
			audit_encode_value(user, name, len);
			format = "op=%s acct=%s old-seuser=%s old-role=%s old-range=%s new-seuser=%s new-role=%s new-range=%s exe=%s hostname=%s addr=%s terminal=%s res=%s";
		} else
			format = "op=%s acct=\"%s\" old-seuser=%s old-role=%s old-range=%s new-seuser=%s new-role=%s new-range=%s exe=%s hostname=%s addr=%s terminal=%s res=%s";
		snprintf(buf, sizeof(buf), format, op, user, 
			old_seuser && strlen(old_seuser) ? old_seuser : "?",
			old_role && strlen(old_role) ? old_role : "?",
			old_range && strlen(old_range) ? old_range : "?",
			new_seuser && strlen(new_seuser) ? new_seuser : "?",
			new_role && strlen(new_role) ? new_role : "?",
			new_range && strlen(new_range) ? new_range : "?",
			pgname,
			host && strlen(host) ? host : "?",
			addrbuf,
			tty && strlen(tty) ? tty : "?",
			success
			);
	} else
		snprintf(buf, sizeof(buf),
		"op=%s id=%u old-seuser=%s old-role=%s old-range=%s new-seuser=%s new-role=%s new-range=%s exe=%s hostname=%s addr=%s terminal=%s res=%s",
			op, id,
			old_seuser && strlen(old_seuser) ? old_seuser : "?",
			old_role && strlen(old_role) ? old_role : "?",
			old_range && strlen(old_range) ? old_range : "?",
			new_seuser && strlen(new_seuser) ? new_seuser : "?",
			new_role && strlen(new_role) ? new_role : "?",
			new_range && strlen(new_range) ? new_range : "?",
			pgname,
			host && strlen(host) ? host : "?",
			addrbuf,
			tty && strlen(tty) ? tty : "?",
			success
			);

	errno = 0;
	ret = audit_send_user_message(audit_fd, type, REAL_ERR, buf);
	if ((ret < 1) && errno == 0)
		errno = ret;
	return ret;
}

/*
 * This function will log a message to the audit system using a predefined
 * message format. This function should be used by all console apps that do
 * not manipulate accounts or groups.
 *
 * audit_fd - The fd returned by audit_open
 * type - type of message, ex: AUDIT_USER_CMD
 * command - the command line being logged
 * tty - The tty of the user
 * result - 1 is "success" and 0 is "failed"
 *
 * It returns the sequence number which is > 0 on success or <= 0 on error.
 */
int audit_log_user_command(int audit_fd, int type, const char *command,
	const char *tty, int result)
{
	char *p;
	char buf[MAX_AUDIT_MESSAGE_LENGTH];
	char commname[PATH_MAX*2];
	char cwdname[PATH_MAX*2];
	char ttyname[TTY_PATH];
	static char exename[PATH_MAX*2] = "";
	char format[64];
	const char *success;
	char *cmd;
	int ret, cwdenc=0, cmdenc=0;
	unsigned int len;

	if (audit_fd < 0)
		return 0;

	if (result)
		success = "success";
	else
		success = "failed";

	if (tty == NULL) 
		tty = _get_tty(ttyname, TTY_PATH);
	else if (*tty == 0)
		tty = NULL;

	if (exename[0] == 0)
		_get_exename(exename, sizeof(exename));

	/* Trim leading spaces */
	while (*command == ' ')
		command++;

	cmd = strdup(command);
	if (cmd == NULL)
		return -1;

	// We borrow the commname buffer
	if (getcwd(commname, PATH_MAX) == NULL)
		strcpy(commname, "?");
	len = strlen(commname);
	if (audit_value_needs_encoding(commname, len)) {
		audit_encode_value(cwdname, commname, len);
		cwdenc = 1;
	} else
		strcpy(cwdname, commname);

	len = strlen(cmd);
	// Trim the trailing carriage return and spaces
	while (len && (cmd[len-1] == 0x0A || cmd[len-1] == ' ')) {
		cmd[len-1] = 0;
		len--;
	}

	if (len >= PATH_MAX) {
		cmd[PATH_MAX] = 0;
		len = PATH_MAX-1;
	}
	if (audit_value_needs_encoding(cmd, len)) {
		audit_encode_value(commname, cmd, len);
		cmdenc = 1;
	}
	if (cmdenc == 0)
		strcpy(commname, cmd);
	free(cmd);

	// Make the format string
	if (cwdenc)
		p=stpcpy(format, "cwd=%s ");
	else
		p=stpcpy(format, "cwd=\"%s\" ");

	if (cmdenc)
		p = stpcpy(p, "cmd=%s ");
	else
		p = stpcpy(p, "cmd=\"%s\" ");

	strcpy(p, "exe=%s terminal=%s res=%s");

	// now use the format string to make the event
	snprintf(buf, sizeof(buf), format,
			cwdname, commname, exename,
			tty ? tty : "?",
			success
		);

	errno = 0;
	ret = audit_send_user_message( audit_fd, type, HIDE_IT, buf );
	if ((ret < 1) && errno == 0)
		errno = ret;
	return ret;
}