/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (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 2005 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */
#pragma ident	"%Z%%M%	%I%	%E% SMI"

#include <fcntl.h>
#include <libscf.h>
#include <secdb.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/file.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <signal.h>
#include <sys/param.h>
#include <unistd.h>
#include <bsm/audit.h>
#include <bsm/libbsm.h>
#include <locale.h>
#include <audit_sig_infc.h>
#include <zone.h>

#if !defined(TEXT_DOMAIN)
#define	TEXT_DOMAIN "SUNW_OST_OSCMD"
#endif

#define	VERIFY -1

/* GLOBALS */
static char	*auditdatafile = AUDITDATAFILE;
static char	*progname = "audit";
static char	*usage = "audit [-n] | [-s] | [-t] | [-v filepath]";
static int	silent = 0;
static char	*instance_name = "svc:/system/auditd:default";

static int	get_auditd_pid();
static void	display_smf_error();

static boolean_t is_audit_control_ok(char *);	/* file validation  */
static boolean_t is_valid_zone(boolean_t);	/* operation ok in this zone? */
static void	start_auditd();			/* start audit daemon */

/*
 * audit() - This program serves as a general administrator's interface to
 *	the audit trail.  Only one option is valid at a time.
 *
 * input:
 *	audit -s
 *		- signal audit daemon to read audit_control file and
 *		  start auditd if needed.
 *	audit -n
 *		- signal audit daemon to use next audit_control audit directory.
 *	audit -t
 *		- signal audit daemon to disable auditing.
 *	audit -T
 *		- signal audit daemon to disable auditing report no errors.
 *	audit -v filepath
 *		- validate audit_control parameters but use filepath for
 *		  the name.  Emit errors or "syntax ok"
 *
 *
 * output:
 *
 * returns:	0 - command successful
 *		>0 - command failed
 */

int
main(int argc, char *argv[])
{
	pid_t pid; /* process id of auditd read from auditdatafile */
	int	sig = 0; /* signal to send auditd */
	char	c;
	char	*first_option;

	/* Internationalization */
	(void) setlocale(LC_ALL, "");
	(void) textdomain(TEXT_DOMAIN);

	if (getuid() != 0) {
		(void) fprintf(stderr, gettext("%s: not super-user\n"),
			progname);
		exit(2);
	}
	/* first option required */
	if ((c = getopt(argc, argv, "nstTv:")) == -1) {
		(void) fprintf(stderr, gettext("usage: %s\n"), usage);
		exit(3);
	}
	first_option = optarg;
	/* second or more options not allowed; please pick one */
	if (getopt(argc, argv, "nstTv:") != -1) {
		(void) fprintf(stderr, gettext("usage: %s\n"), usage);
		exit(5);
	}
	switch (c) {
	case 'n':
		if (!is_valid_zone(1))	/* 1 == display error if any */
			exit(10);

		sig = AU_SIG_NEXT_DIR;
		break;
	case 's':
		if (!is_valid_zone(1))	/* 1 == display error if any */
			exit(10);
		else if (!is_audit_control_ok(NULL))
			exit(7);

		start_auditd();
		break;
	case 't':
		if (!is_valid_zone(0))	/* 0 == no error message display */
			exit(0);
		/* use  bmsunconv to permanently disable, -t for temporary */
		if (smf_disable_instance(instance_name, SMF_TEMPORARY) != 0)
			display_smf_error();
		break;
	case 'T':
		if (!is_valid_zone(0))	/* 0 == no error message display */
			exit(0);

		(void) smf_disable_instance(instance_name, SMF_TEMPORARY);
		silent = 1;
		break;
	case 'v':
		if (is_audit_control_ok(first_option)) {
			(void) fprintf(stderr, gettext("syntax ok\n"));
			exit(0);
		} else {
			exit(8);
		}
		break;
	default:
		(void) fprintf(stderr, gettext("usage: %s\n"), usage);
		exit(6);
	}

	if (get_auditd_pid(&pid) != 0) {
		if (silent) {
			exit(0);
		} else {
			(void) fprintf(stderr, "%s: %s\n", progname, gettext(
			"can't get process id of auditd from audit_data(4)"));
			exit(4);
		}
	}

	if ((sig != 0) && (kill(pid, sig) != 0)) {
		if (silent) {
			exit(0);
		} else {
			perror(progname);
			(void) fprintf(stderr,
			    gettext("%s: cannot signal auditd\n"), progname);
			exit(1);
		}
	}
	return (0);
}


/*
 * get_auditd_pid(&pid):
 *
 * reads PID from audit_data
 *
 * returns:	0 - successful
 *		1 - error
 */

static int
get_auditd_pid(pid_t *p_pid)
{
	FILE	*adp;		/* audit_data file pointer */
	int	retstat;

	if ((adp = fopen(auditdatafile, "r")) == NULL) {
		if (!silent)
			perror(progname);
		return (1);
	}
	retstat = (fscanf(adp, "%ld", p_pid) != 1);
	(void) fclose(adp);
	return (retstat);
}

/*
 * perform reasonableness check on audit_control or its standin; goal
 * is that "audit -s" (1) not crash the system and (2) c2audit/auditd
 * actually generates data.
 *
 * A NULL input is ok -- it is used to tell _openac() to use the
 * real audit_control file, not a substitute.
 */
#define	TRADITIONAL_MAX	1024

static boolean_t
is_audit_control_ok(char *filename) {
	char		buf[TRADITIONAL_MAX];
	int		outputs = 0;
	int		state = 1;	/* 1 is ok, 0 is not */
	int		rc;
	int		min;
	kva_t		*kvlist;
	char		*value;
	au_acinfo_t	*ach;

	ach = _openac(filename);	/* open audit_control */
	if (ach == NULL) {
		perror(progname);
		exit(9);
	}
	/*
	 * There must be at least one directory or one plugin
	 * defined.
	 */
	if ((rc = _getacdir(ach, buf, TRADITIONAL_MAX)) == 0) {
		outputs++;
	} else if (rc < -1) {	/* -1 is not found, others are errors */
		(void) fprintf(stderr,
			gettext("%s: audit_control \"dir:\" spec invalid\n"),
				progname);
		state = 0;	/* is_not_ok */
	}

	/*
	 * _getacplug -- all that is of interest is the return code.
	 */
	_rewindac(ach);	/* rewind audit_control */
	if ((rc = _getacplug(ach, &kvlist)) == 0) {
		value = kva_match(kvlist, "name");
		if (value == NULL) {
			(void) fprintf(stderr, gettext("%s: audit_control "
			    "\"plugin:\" missing name\n"), progname);
			state = 0;	/* is_not_ok */
		}
		else
			outputs++;

		_kva_free(kvlist);
	} else if (rc < -1) {
		(void) fprintf(stderr,
			gettext("%s: audit_control \"plugin:\" spec invalid\n"),
				progname);
		state = 0;	/* is_not_ok */
	}
	if (outputs == 0) {
		(void) fprintf(stderr,
			gettext("%s: audit_control must have either a "
				"\"dir:\" or a \"plugin:\" specified.\n"),
				progname);
		state = 0;	/* is_not_ok */
	}
	/* minfree is not required */
	_rewindac(ach);
	if ((rc = _getacmin(ach, &min)) < -1) {
		(void) fprintf(stderr,
			gettext(
			    "%s: audit_control \"minfree:\" spec invalid\n"),
			    progname);
		state = 0;	/* is_not_ok */
	}
	/* flags is not required */
	_rewindac(ach);
	if ((rc = _getacflg(ach, buf, TRADITIONAL_MAX)) < -1) {
		(void) fprintf(stderr,
			gettext("%s: audit_control \"flags:\" spec invalid\n"),
				progname);
		state = 0;	/* is_not_ok */
	}
	/* naflags is not required */
	_rewindac(ach);
	if ((rc = _getacna(ach, buf, TRADITIONAL_MAX)) < -1) {
		(void) fprintf(stderr,
			gettext(
			    "%s: audit_control \"naflags:\" spec invalid\n"),
			    progname);
		state = 0;	/* is_not_ok */
	}
	_endac(ach);
	return (state);
}

/*
 * The operations that call this function are only valid in the global
 * zone unless the perzone audit policy is set.
 *
 * "!silent" and "show_err" are slightly different; silent is from
 * -T for which no error messages should be displayed and show_err
 * applies to more options (including -T)
 *
 */

static boolean_t
is_valid_zone(boolean_t show_err)
{
	long	policy;

	if (auditon(A_GETPOLICY, (char *)&policy, 0) == -1) {
		if (!silent)
			(void) fprintf(stderr, gettext(
			    "%s: Cannot read audit policy:  %s\n"),
			    progname, strerror(errno));
		return (0);
	}
	if (policy & AUDIT_PERZONE)
		return (1);

	if (getzoneid() != GLOBAL_ZONEID) {
		if (show_err)
			(void) fprintf(stderr,
			    gettext("%s: Not valid in a local zone.\n"),
			    progname);
		return (0);
	} else {
		return (1);
	}
}

/*
 * if auditd isn't running, start it.  Otherwise refresh.
 * First check to see if c2audit is loaded via the auditon()
 * system call, then check SMF state.
 */
static void
start_auditd()
{
	int	audit_state;
	char	*state;

	if (auditon(A_GETCOND, (caddr_t)&audit_state,
	    sizeof (audit_state)) != 0)
		return;

	if ((state = smf_get_state(instance_name)) == NULL) {
		display_smf_error();
		return;
	}
	if (strcmp(SCF_STATE_STRING_ONLINE, state) != 0) {
		if (smf_enable_instance(instance_name, 0) != 0)
			display_smf_error();
	} else {
		if (smf_refresh_instance(instance_name) != 0)
			display_smf_error();
	}
	free(state);
}

static void
display_smf_error()
{
	int	rc = scf_error();

	switch (rc) {
	case SCF_ERROR_NOT_FOUND:
		(void) fprintf(stderr,
		    "SMF error: \"%s\" not found.\n",
		    instance_name);
		break;
	default:
		(void) fprintf(stderr, "SMF error %d\n", rc);
		break;
	}
}