/* auditctl.c -- 
 * Copyright 2004-2017,2020 Red Hat Inc.
 * All Rights Reserved.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; 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>
 *     Rickard E. (Rik) Faith <faith@redhat.com>
 *     Richard Guy Briggs <rgb@redhat.com>
 */

#include "config.h"
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>	/* strdup needs xopen define */
#include <getopt.h>
#include <time.h>
#include <sys/stat.h>
#include <ctype.h>
#include <unistd.h>
#include <sys/utsname.h>
#include <sys/select.h>
#include <sys/time.h>
#include <fcntl.h>
#include <errno.h>
#include <libgen.h>	/* For basename */
#include <limits.h>	/* PATH_MAX */
#include <signal.h>
#include "libaudit.h"
#include "auditctl-listing.h"
#include "private.h"
#include "common.h"

/* This define controls the size of the line that we will request when
 * reading in rules from a file.
 */
#define LINE_SIZE 6144


/* Global functions */
static int handle_request(int status);
static void get_reply(void);
extern int delete_all_rules(int fd);

/* Global vars */
int list_requested = 0, interpret = 0;
char key[AUDIT_MAX_KEY_LEN+1];
const char key_sep[2] = { AUDIT_KEY_SEPARATOR, 0 };
static unsigned int keylen;
static int fd = -1;
static int add = AUDIT_FILTER_UNSET, del = AUDIT_FILTER_UNSET, action = -1;
static int ignore = 0, continue_error = 0;
static int exclude = 0;
static int multiple = 0;
static struct audit_rule_data *rule_new = NULL;

/*
 * This function will reset everything used for each loop when loading
 * a ruleset from a file.
 */
static int reset_vars(void)
{
	list_requested = 0;
	_audit_syscalladded = 0;
	_audit_permadded = 0;
	_audit_archadded = 0;
	_audit_exeadded = 0;
	_audit_filterfsadded = 0;
	_audit_elf = 0;
	add = AUDIT_FILTER_UNSET;
	del = AUDIT_FILTER_UNSET;
	action = -1;
	exclude = 0;
	multiple = 0;

	audit_rule_free_data(rule_new);
	rule_new = audit_rule_create_data();
	if (fd < 0) {
		if ((fd = audit_open()) < 0) {
			audit_msg(LOG_ERR, "Cannot open netlink audit socket");
			return 1;
		}
	}
	return 0;
}

static void usage(void)
{
    printf(
    "usage: auditctl [options]\n"
     "    -a <l,a>                          Append rule to end of <l>ist with <a>ction\n"
     "    -A <l,a>                          Add rule at beginning of <l>ist with <a>ction\n"
     "    -b <backlog>                      Set max number of outstanding audit buffers\n"
     "                                      allowed Default=64\n"
     "    -c                                Continue through errors in rules\n"
     "    -C f=f                            Compare collected fields if available:\n"
     "                                      Field name, operator(=,!=), field name\n"
     "    -d <l,a>                          Delete rule from <l>ist with <a>ction\n"
     "                                      l=task,exit,user,exclude,filesystem\n"
     "                                      a=never,always\n"
     "    -D                                Delete all rules and watches\n"
     "    -e [0..2]                         Set enabled flag\n"
     "    -f [0..2]                         Set failure flag\n"
     "                                      0=silent 1=printk 2=panic\n"
     "    -F f=v                            Build rule: field name, operator(=,!=,<,>,<=,\n"
     "                                      >=,&,&=) value\n"
     "    -h                                Help\n"
     "    -i                                Ignore errors when reading rules from file\n"
     "    -k <key>                          Set filter key on audit rule\n"
     "    -l                                List rules\n"
     "    -m text                           Send a user-space message\n"
     "    -p [r|w|x|a]                      Set permissions filter on watch\n"
     "                                      r=read, w=write, x=execute, a=attribute\n"
     "    -q <mount,subtree>                make subtree part of mount point's dir watches\n"
     "    -r <rate>                         Set limit in messages/sec (0=none)\n"
     "    -R <file>                         read rules from file\n"
     "    -s                                Report status\n"
     "    -S syscall                        Build rule: syscall name or number\n"
     "    --signal <signal>                 Send the specified signal to the daemon\n"
     "    -t                                Trim directory watches\n"
     "    -v                                Version\n"
     "    -w <path>                         Insert watch at <path>\n"
     "    -W <path>                         Remove watch at <path>\n"
#if HAVE_DECL_AUDIT_FEATURE_VERSION == 1
     "    --loginuid-immutable              Make loginuids unchangeable once set\n"
#endif
#if HAVE_DECL_AUDIT_VERSION_BACKLOG_WAIT_TIME == 1 || \
    HAVE_DECL_AUDIT_STATUS_BACKLOG_WAIT_TIME == 1
     "    --backlog_wait_time               Set the kernel backlog_wait_time\n"
#endif
#if defined(HAVE_STRUCT_AUDIT_STATUS_FEATURE_BITMAP)
     "    --reset-lost                      Reset the lost record counter\n"
#endif
#if HAVE_DECL_AUDIT_STATUS_BACKLOG_WAIT_TIME_ACTUAL == 1
     "    --reset_backlog_wait_time_actual  Reset the actual backlog wait time counter\n"
#endif
     );
}

static int lookup_filter(const char *str, int *filter)
{
	if (strcmp(str, "exit") == 0)
		*filter = AUDIT_FILTER_EXIT;
	else if (strcmp(str, "task") == 0)
		*filter = AUDIT_FILTER_TASK;
	else if (strcmp(str, "user") == 0)
		*filter = AUDIT_FILTER_USER;
	else if (strcmp(str, "exclude") == 0) {
		*filter = AUDIT_FILTER_EXCLUDE;
		exclude = 1;
	} else if (strcmp(str, "filesystem") == 0)
		*filter = AUDIT_FILTER_FS;
	else
		return 2;
	return 0;
}

static int lookup_action(const char *str, int *act)
{
	if (strcmp(str, "always") == 0)
		*act = AUDIT_ALWAYS;
	else if (strcmp(str, "never") == 0)
		*act = AUDIT_NEVER;
	else if (strcmp(str, "possible") == 0)
		return 1;
	else
		return 2;
	return 0;
}

/*
 * Returns 0 ok, 1 deprecated action, 2 rule error,
 * 3 multiple rule insert/delete
 */
static int audit_rule_setup(char *opt, int *filter, int *act)
{
	int rc;
	char *p;

	if (++multiple != 1)
		return 3;

	p = strchr(opt, ',');
	if (p == NULL || strchr(p+1, ','))
		return 2;
	*p = 0;

	/* Try opt both ways */
	if (lookup_action(opt, act) == 2) {
		rc = lookup_filter(opt, filter);
		if (rc != 0) {
			*p = ',';
			return rc;
		}
	}

	/* Repair the string */
	*p = ',';
	opt = p+1;

	/* If flags are empty, p+1 must be the filter */
	if (*filter == AUDIT_FILTER_UNSET)
		lookup_filter(opt, filter);
	else {
		rc = lookup_action(opt, act);
		if (rc != 0)
			return rc;
	}

	/* Make sure we set both */
	if (*filter == AUDIT_FILTER_UNSET || *act == -1)
		return 2;

	return 0;
}

/*
 * This function will check the path before accepting it. It returns
 * 1 on error and 0 on success.
 */
static int check_path(const char *path)
{
	char *ptr, *base;
	size_t nlen;
	size_t plen = strlen(path);
	if (plen >= PATH_MAX) {
		audit_msg(LOG_ERR, "The path passed for the watch is too big");
		return 1;
	}
	if (path[0] != '/') {
		audit_msg(LOG_ERR, "The path must start with '/'");
		return 1;
	}
	ptr = strdup(path);
	base = basename(ptr);
	nlen = strlen(base);
	free(ptr);
	if (nlen > NAME_MAX) {
		audit_msg(LOG_ERR, "The base name of the path is too big");
		return 1;
	}

	/* These are warnings, not errors */
	if (strstr(path, ".."))
		audit_msg(LOG_WARNING, 
			"Warning - relative path notation is not supported");
	if (strchr(path, '*') || strchr(path, '?'))
		audit_msg(LOG_WARNING, 
			"Warning - wildcard notation is not supported");

	return 0;
}

/*
 * Setup a watch.  The "name" of the watch in userspace will be the <path> to
 * the watch.  When this potential watch reaches the kernel, it will resolve
 * down to <name> (of terminating file or directory). 
 * Returns a 1 on success & -1 on failure.
 */
static int audit_setup_watch_name(struct audit_rule_data **rulep, char *path)
{
	int type = AUDIT_WATCH;
	size_t len;
	struct stat buf;

	if (check_path(path))
		return -1;

	// Trim trailing '/' should they exist
	len = strlen(path);
	if (len > 2 && path[len-1] == '/') {
		while (path[len-1] == '/' && len > 1) {
			path[len-1] = 0;
			len--;
		}
	}
	if (stat(path, &buf) == 0) {
		if (S_ISDIR(buf.st_mode))
			type = AUDIT_DIR;
	}
	/* FIXME: might want to check to see that rule is empty */
	if (audit_add_watch_dir(type, rulep, path)) 
		return -1;

	return 1;
}

/*
 * Setup a watch permissions.
 * Returns a 1 on success & -1 on failure.
 */
static int audit_setup_perms(struct audit_rule_data *rule, const char *opt)
{
	unsigned int i, len, val = 0;

	len = strlen(opt);
	if (len > 4) {
		audit_msg(LOG_ERR, "permission %s is too long", opt);
		return -1;
	}

	for (i = 0; i < len; i++) {
		switch (tolower(opt[i])) {
			case 'r':
				val |= AUDIT_PERM_READ;
				break;
			case 'w':
				val |= AUDIT_PERM_WRITE;
				break;
			case 'x':
				val |= AUDIT_PERM_EXEC;
				break;
			case 'a':
				val |= AUDIT_PERM_ATTR;
				break;
			default:
				audit_msg(LOG_ERR,
					"Permission %c isn't supported",
					opt[i]);
				return -1;
		}
	}

	if (audit_update_watch_perms(rule_new, val) == 0) {
		_audit_permadded = 1;
		return 1;
	}
	return -1;
}

static int equiv_parse(char *optarg, char **mp, char **sub)
{
	char *ptr = strchr(optarg, ',');
	if (ptr == NULL)
		return -1;	// no comma
	*ptr = 0;
	ptr++;
	if (*ptr == 0)
		return -1;	// ends with comma
	*mp = optarg;
	*sub = ptr;
	if (strchr(*sub, ','))
		return -1;	// too many commas
	return 0;
}

static int audit_request_rule_list(void)
{
	if (audit_request_rules_list_data(fd) > 0) {
		list_requested = 1;
		get_reply();
		return 1;
	}
	return 0;
}

static int check_rule_mismatch(int lineno, const char *option)
{
	struct audit_rule_data tmprule;
	unsigned int old_audit_elf = _audit_elf;
	int rc = 0;

	switch (_audit_elf)
	{
		case AUDIT_ARCH_X86_64:
			_audit_elf = AUDIT_ARCH_I386;
			break;
		case AUDIT_ARCH_PPC64:
			_audit_elf = AUDIT_ARCH_PPC;
			break;
		case AUDIT_ARCH_S390X:
			_audit_elf = AUDIT_ARCH_S390;
			break;
	}

	char *ptr, *saved, *tmp = strdup(option);
	if (tmp == NULL)
		return -1;
	ptr = strtok_r(tmp, ",", &saved);
	memset(&tmprule, 0, sizeof(struct audit_rule_data));
	while (ptr) {
		audit_rule_syscallbyname_data(&tmprule, ptr);
		ptr = strtok_r(NULL, ",", &saved);
	}
	if (memcmp(tmprule.mask, rule_new->mask, AUDIT_BITMASK_SIZE))
		rc = 1;
	free(tmp);

	_audit_elf = old_audit_elf;
	if (rc) {
		if (lineno)
			audit_msg(LOG_WARNING, "WARNING - 32/64 bit syscall mismatch in line %d, you should specify an arch", lineno);
		else
			audit_msg(LOG_WARNING, "WARNING - 32/64 bit syscall mismatch, you should specify an arch");
	}
	return 0;
}


static int send_signal(const char *optarg)
{
	int signal = 0, retval, i;
	int timeout = 40; /* loop has delay of .1 - so this is 4 seconds */
	struct audit_reply rep;

	fd_set read_mask;
	FD_ZERO(&read_mask);
	FD_SET(fd, &read_mask);

	if (strcasecmp(optarg, "TERM") == 0)
		signal = SIGTERM;
	else if (strcasecmp(optarg, "HUP") == 0)
		signal = SIGHUP;
	else if (strcasecmp(optarg, "USR1") == 0)
		signal = SIGUSR1;
	else if (strcasecmp(optarg, "USR2") == 0)
		signal = SIGUSR2;
	else if (strcasecmp(optarg, "CONT") == 0)
		signal = SIGCONT;

	if (signal == 0) {
		audit_msg(LOG_ERR, "%s is an unsupported signal", optarg);
		return -1;
	}

	// Request status so that we can find the pid
	retval = audit_request_status(fd);
	if (retval == -1) {
		if (errno == ECONNREFUSED)
			audit_msg(LOG_INFO, "The audit system is disabled");
		return -1;
	}

	// Receive the netlink info
	for (i = 0; i < timeout; i++) {
		struct timeval t;

		t.tv_sec  = 0;
		t.tv_usec = 100000; /* .1 second */
		do {
			retval = select(fd+1, &read_mask, NULL, NULL, &t);
		} while (retval < 0 && errno == EINTR);

		// We'll try to read just in case
		retval = audit_get_reply(fd, &rep, GET_REPLY_NONBLOCKING, 0);
		if (retval > 0) {
			if (rep.type == NLMSG_ERROR && rep.error->error == 0) {
				i = 0;    /* reset timeout */
				continue; /* This was an ack */
			}

			if (rep.type == NLMSG_NOOP) {
				i = 0; /* If getting more, reset timeout */
				continue;
			} else if (rep.type == NLMSG_DONE)
				break;
			else if (rep.type == AUDIT_GET) {
				if (rep.status->pid == 0) {
					audit_msg(LOG_INFO,
						"Auditd is not running");
					return -2;
				}
				retval = kill(rep.status->pid, signal);
				if (retval < 0) {
					audit_msg(LOG_WARNING,
				        "Failed sending signal to auditd (%s)",
						 strerror(errno));
					return -1;
				} else
					return -2;
			}
		}
	}
	audit_msg(LOG_WARNING, "Failed sending signal to auditd (timeout)");
	return -1;
}

static int report_status(void)
{
	int retval;

	retval = audit_request_status(fd);
	if (retval == -1) {
		if (errno == ECONNREFUSED)
			fprintf(stderr,	"The audit system is disabled\n");
		return -1;
	}
	get_reply();
	retval = audit_request_features(fd);
	if (retval == -1) {
		// errno is EINVAL if the kernel does support features API
		if (errno == EINVAL)
			return -2;
		return -1;
	}
	get_reply();
	return -2;
}

static int parse_syscall(const char *optarg)
{
	int retval = 0;
	char *saved;

	if (strchr(optarg, ',')) {
		char *ptr, *tmp = strdup(optarg);
		if (tmp == NULL)
			return -1;
		ptr = strtok_r(tmp, ",", &saved);
		while (ptr) {
			retval = audit_rule_syscallbyname_data(rule_new, ptr);
			if (retval != 0) {
				if (retval == -1) {
					audit_msg(LOG_ERR,
						"Syscall name unknown: %s", 
						ptr);
					retval = -3; // error reported
				}
				break;
			}
			ptr = strtok_r(NULL, ",", &saved);
		}
		free(tmp);
		return retval;
	}

	return audit_rule_syscallbyname_data(rule_new, optarg);
}

static struct option long_opts[] =
{
#if HAVE_DECL_AUDIT_FEATURE_VERSION == 1
  {"loginuid-immutable", 0, NULL, 1},
#endif
#if HAVE_DECL_AUDIT_VERSION_BACKLOG_WAIT_TIME == 1 || \
    HAVE_DECL_AUDIT_STATUS_BACKLOG_WAIT_TIME == 1
  {"backlog_wait_time", 1, NULL, 2},
#endif
#if defined(HAVE_STRUCT_AUDIT_STATUS_FEATURE_BITMAP)
  {"reset-lost", 0, NULL, 3},
#endif
#if HAVE_DECL_AUDIT_STATUS_BACKLOG_WAIT_TIME_ACTUAL == 1
  {"reset_backlog_wait_time_actual", 0, NULL, 4},
#endif
  {"signal", 1, NULL, 5},
  {NULL, 0, NULL, 0}
};

// FIXME: Change these to enums
/*
 * returns: -3 deprecated, -2 success - no reply, -1 error - noreply,
 * 0 success - reply, > 0 success - rule
 */
static int setopt(int count, int lineno, char *vars[])
{
    int c, lidx = 0;
    int retval = 0, rc;

    optind = 0;
    opterr = 0;
    key[0] = 0;
    keylen = AUDIT_MAX_KEY_LEN;

    while ((retval >= 0) && (c = getopt_long(count, vars,
			"hicslDvtC:e:f:r:b:a:A:d:S:F:m:R:w:W:k:p:q:",
			long_opts, &lidx)) != EOF) {
	int flags = AUDIT_FILTER_UNSET;
	rc = 10;	// Init to something impossible to see if unused.
        switch (c) {
        case 'h':
		usage();
		retval = -1;
		break;
	case 'i':
		ignore = 1;
		retval = -2;
		break;
	case 'c':
		ignore = 1;
		continue_error = 1;
		retval = -2;
		break;
        case 's':
		if (count > 3) {
			audit_msg(LOG_ERR,
				"Too many options for status command");
			retval = -1;
			break;
		} else if (optind == 2 && count == 3) { 
			if (strcmp(vars[optind], "-i") == 0) {
				interpret = 1;
				count -= 1;
			} else {
				audit_msg(LOG_ERR,
					"Only -i option is allowed");
				retval = -1;
				break;
			}
		}
		retval = report_status();
		break;
        case 'e':
		if (optarg && ((strcmp(optarg, "0") == 0) ||
				(strcmp(optarg, "1") == 0) ||
				(strcmp(optarg, "2") == 0))) {
			if (audit_set_enabled(fd, strtoul(optarg,NULL,0)) > 0)
				audit_request_status(fd);
			else
				retval = -1;
		} else {
			audit_msg(LOG_ERR, "Enable must be 0, 1, or 2 was %s", 
				optarg);
			retval = -1;
		}
		break;
        case 'f':
		if (optarg && ((strcmp(optarg, "0") == 0) ||
				(strcmp(optarg, "1") == 0) ||
				(strcmp(optarg, "2") == 0))) {
			if (audit_set_failure(fd, strtoul(optarg,NULL,0)) > 0)
				audit_request_status(fd);
			else
				return -1;
		} else {
			audit_msg(LOG_ERR, "Failure must be 0, 1, or 2 was %s", 
				optarg);
			retval = -1;
		}
		break;
        case 'r':
		if (optarg && isdigit(optarg[0])) { 
			uint32_t rate;
			errno = 0;
			rate = strtoul(optarg,NULL,0);
			if (errno) {
				audit_msg(LOG_ERR, "Error converting rate");
				return -1;
			}
			if (audit_set_rate_limit(fd, rate) > 0)
				audit_request_status(fd);
			else
				return -1;
		} else {
			audit_msg(LOG_ERR,"Rate must be a numeric value was %s",
				optarg);
			retval = -1;
		}
		break;
        case 'b':
		if (optarg && isdigit(optarg[0])) {
			uint32_t limit;
			errno = 0;
			limit = strtoul(optarg,NULL,0);
			if (errno) {
				audit_msg(LOG_ERR, "Error converting backlog");
				return -1;
			}
			if (audit_set_backlog_limit(fd, limit) > 0)
				audit_request_status(fd);
			else
				return -1;
		} else {
			audit_msg(LOG_ERR, 
				"Backlog must be a numeric value was %s", 
				optarg);
			retval = -1;
		}
		break;
        case 'l':
		if (count > 4) {
			audit_msg(LOG_ERR,
				"Wrong number of options for list request");
			retval = -1;
			break;
		}
		if (count == 3) {
			if (strcmp(vars[optind], "-i") == 0) {
				interpret = 1;
				count -= 1;
			} else {
				audit_msg(LOG_ERR,
					"Only -k or -i options are allowed");
				retval = -1;
				break;
			}
		} else if (count == 4) {
			if (vars[optind] && strcmp(vars[optind], "-k") == 0) {
				strncat(key, vars[3], keylen);
				count -= 2;
			} else {
				audit_msg(LOG_ERR,
					"Only -k or -i options are allowed");
				retval = -1;
				break;
			}
		}
		if (audit_request_rule_list()) {
			list_requested = 1;
			retval = -2;
		} else
			retval = -1;
		break;
        case 'a':
		if (strstr(optarg, "task") && _audit_syscalladded) {
			audit_msg(LOG_ERR,
				"Syscall auditing requested for task list");
			retval = -1;
		} else {
			rc = audit_rule_setup(optarg, &add, &action);
			if (rc == 3) {
				audit_msg(LOG_ERR,
		"Multiple rule insert/delete operations are not allowed\n");
				retval = -1;
			} else if (rc == 2) {
				audit_msg(LOG_ERR,
					"Append rule - bad keyword %s",
					optarg);
				retval = -1;
			} else if (rc == 1) {
				audit_msg(LOG_ERR,
				    "Append rule - possible is deprecated");
				return -3; /* deprecated - eat it */
			} else
				retval = 1; /* success - please send */
		}
		break;
        case 'A': 
		if (strstr(optarg, "task") && _audit_syscalladded) {
			audit_msg(LOG_ERR, 
			   "Error: syscall auditing requested for task list");
			retval = -1;
		} else {
			rc = audit_rule_setup(optarg, &add, &action);
			if (rc == 3) {
				audit_msg(LOG_ERR,
		"Multiple rule insert/delete operations are not allowed");
				retval = -1;
			} else if (rc == 2) {
				audit_msg(LOG_ERR,
				"Add rule - bad keyword %s", optarg);
				retval = -1;
			} else if (rc == 1) {
				audit_msg(LOG_WARNING, 
				    "Append rule - possible is deprecated");
				return -3; /* deprecated - eat it */
			} else {
				add |= AUDIT_FILTER_PREPEND;
				retval = 1; /* success - please send */
			}
		}
		break;
        case 'd': 
		rc = audit_rule_setup(optarg, &del, &action);
		if (rc == 3) {
			audit_msg(LOG_ERR,
		"Multiple rule insert/delete operations are not allowed");
			retval = -1;
		} else if (rc == 2) {
			audit_msg(LOG_ERR, "Delete rule - bad keyword %s", 
				optarg);
			retval = -1;
		} else if (rc == 1) {
			audit_msg(LOG_INFO, 
			    "Delete rule - possible is deprecated");
			return -3; /* deprecated - eat it */
		} else
			retval = 1; /* success - please send */
		break;
        case 'S': {
		int unknown_arch = !_audit_elf;
		/* Do some checking to make sure that we are not adding a
		 * syscall rule to a list that does not make sense. */
		if (((add & (AUDIT_FILTER_MASK|AUDIT_FILTER_UNSET)) ==
				AUDIT_FILTER_TASK || (del & 
				(AUDIT_FILTER_MASK|AUDIT_FILTER_UNSET)) == 
				AUDIT_FILTER_TASK)) {
			audit_msg(LOG_ERR, 
			  "Error: syscall auditing being added to task list");
			return -1;
		} else if (((add & (AUDIT_FILTER_MASK|AUDIT_FILTER_UNSET)) ==
				AUDIT_FILTER_USER || (del &
				(AUDIT_FILTER_MASK|AUDIT_FILTER_UNSET)) ==
				AUDIT_FILTER_USER)) {
			audit_msg(LOG_ERR, 
			  "Error: syscall auditing being added to user list");
			return -1;
		} else if (((add & (AUDIT_FILTER_MASK|AUDIT_FILTER_UNSET)) ==
				AUDIT_FILTER_FS || (del &
				(AUDIT_FILTER_MASK|AUDIT_FILTER_UNSET)) ==
				AUDIT_FILTER_FS)) {
			audit_msg(LOG_ERR, 
			  "Error: syscall auditing being added to filesystem list");
			return -1;
		} else if (exclude) {
			audit_msg(LOG_ERR, 
		    "Error: syscall auditing cannot be put on exclude list");
			return -1;
		} else {
			if (unknown_arch) {
				int machine;
				unsigned int elf;
				machine = audit_detect_machine();
				if (machine < 0) {
					audit_msg(LOG_ERR, 
					    "Error detecting machine type");
					return -1;
				}
				elf = audit_machine_to_elf(machine);
                                if (elf == 0) {
					audit_msg(LOG_ERR, 
					    "Error looking up elf type %d",
						machine);
					return -1;
				}
				_audit_elf = elf;
			}
		}
		rc = parse_syscall(optarg);
		switch (rc)
		{
			case 0:
				_audit_syscalladded = 1;
				if (unknown_arch && add != AUDIT_FILTER_UNSET)
					if (check_rule_mismatch(lineno, optarg) == -1)
						retval = -1;
				break;
			case -1:
				audit_msg(LOG_ERR, "Syscall name unknown: %s", 
							optarg);
				retval = -1;
				break;
			case -2:
				audit_msg(LOG_ERR, "Elf type unknown: 0x%x", 
							_audit_elf);
				retval = -1;
				break;
			case -3: // Error reported - do nothing here
				retval = -1;
				break;
		}}
		break;
        case 'F':
		if (add != AUDIT_FILTER_UNSET)
			flags = add & AUDIT_FILTER_MASK;
		else if (del != AUDIT_FILTER_UNSET)
			flags = del & AUDIT_FILTER_MASK;
		// if the field is arch & there is a -t option...we 
		// can allow it
		else if ((optind >= count) || (strstr(optarg, "arch=") == NULL)
				 || (strcmp(vars[optind], "-t") != 0)) {
			audit_msg(LOG_ERR, "List must be given before field");
			retval = -1;
			break;
		}

		// Keys need to get handled differently
		if (strncmp(optarg, "key=", 4) == 0) {
			optarg += 4;
			goto process_keys;
		}
		rc = audit_rule_fieldpair_data(&rule_new,optarg,flags);
		if (rc != 0) {
			audit_number_to_errmsg(rc, optarg);
			retval = -1;
		} else {
			if (rule_new->fields[rule_new->field_count-1] ==
						AUDIT_PERM)
				_audit_permadded = 1;
			if (rule_new->fields[rule_new->field_count-1] ==
						AUDIT_EXE) 
				_audit_exeadded = 1;
		}

		break;
	case 'C':
		if (add != AUDIT_FILTER_UNSET)
			flags = add & AUDIT_FILTER_MASK;
		else if (del != AUDIT_FILTER_UNSET)
			flags = del & AUDIT_FILTER_MASK;

		rc = audit_rule_interfield_comp_data(&rule_new, optarg, flags);
		if (rc != 0) {
			audit_number_to_errmsg(rc, optarg);
			retval = -1;
		} else {
			if (rule_new->fields[rule_new->field_count - 1] ==
			    AUDIT_PERM)
				_audit_permadded = 1;
		}
		break;
        case 'm':
		if (count > 3) {
			audit_msg(LOG_ERR,
	  "The -m option must be only the only option and takes 1 parameter");
			retval = -1;
		} else {
			const char*s = optarg;
			char *umsg;
			while (*s) {
				if (*s < 32) {
					audit_msg(LOG_ERR,
					"Illegal character in audit event");
					return -1;
				}
				s++;
			}
			if (asprintf(&umsg, "text=%s", optarg) < 0) {
				audit_msg(LOG_ERR, "Can't create user event");
				return -1;
			}
			if (audit_log_user_message( fd, AUDIT_USER,
					umsg, NULL, NULL, NULL, 1) <= 0)
				retval = -1;
			else {
				free(umsg);
				return -2;  // success - no reply for this
			}
			free(umsg);
		}
		break;
	case 'R':
		audit_msg(LOG_ERR, "Error - nested rule files not supported");
		retval = -1;
		break;
	case 'D':
		if (count > 4 || count == 3) {
			audit_msg(LOG_ERR,
			    "Wrong number of options for Delete all request");
			retval = -1;
			break;
		}
		if (count == 4) {
			if (strcmp(vars[optind], "-k") == 0) {
				strncat(key, vars[3], keylen);
				count -= 2;
			} else {
				audit_msg(LOG_ERR,
					"Only the -k option is allowed");
				retval = -1;
				break;
			}
		}
		retval = delete_all_rules(fd);
		if (retval == 0) {
			(void)audit_request_rule_list();
			key[0] = 0;
			retval = -2;
		}
		break;
	case 'w':
		if (add != AUDIT_FILTER_UNSET ||
			del != AUDIT_FILTER_UNSET) {
			audit_msg(LOG_ERR,
				"watch option can't be given with a syscall");
			retval = -1;
		} else if (optarg) {
			add = AUDIT_FILTER_EXIT;
			action = AUDIT_ALWAYS;
			_audit_syscalladded = 1;
			retval = audit_setup_watch_name(&rule_new, optarg);
		} else {
			audit_msg(LOG_ERR, "watch option needs a path");
			retval = -1;
		}
		break;
	case 'W':
		if (optarg) { 
			del = AUDIT_FILTER_EXIT;
			action = AUDIT_ALWAYS;
			_audit_syscalladded = 1;
			retval = audit_setup_watch_name(&rule_new, optarg);
		} else {
			audit_msg(LOG_ERR, "watch option needs a path");
			retval = -1;
		}
		break;
	case 'k':
		if (!(_audit_syscalladded || _audit_permadded ||
		      _audit_exeadded ||
		      _audit_filterfsadded) ||
		    (add==AUDIT_FILTER_UNSET && del==AUDIT_FILTER_UNSET)) {
			audit_msg(LOG_ERR,
		    "key option needs a watch or syscall given prior to it");
			retval = -1;
			break;
		} else if (!optarg) {
			audit_msg(LOG_ERR, "key option needs a value");
			retval = -1;
			break;
		}
process_keys:
		if ((strlen(optarg)+strlen(key)+(!!key[0])) >
							AUDIT_MAX_KEY_LEN) {
			audit_msg(LOG_ERR, "key option exceeds size limit");
			retval = -1;
		} else {
			if (strchr(optarg, AUDIT_KEY_SEPARATOR))
				audit_msg(LOG_ERR,
				    "key %s has illegal character", optarg);
			if (key[0]) { // Add the separator if we need to
				strcat(key, key_sep);
				keylen--;
			}
			strncat(key, optarg, keylen);
			keylen = AUDIT_MAX_KEY_LEN - strlen(key);
		}
		break;
	case 'p':
		if (add == AUDIT_FILTER_UNSET && del == AUDIT_FILTER_UNSET) {
			audit_msg(LOG_ERR,
			"permission option needs a watch given prior to it");
			retval = -1;
		} else if (!optarg) {
			audit_msg(LOG_ERR, "permission option needs a filter");
			retval = -1;
		} else 
			retval = audit_setup_perms(rule_new, optarg);
		break;
        case 'q':
		if (_audit_syscalladded) {
			audit_msg(LOG_ERR, 
			   "Syscall auditing requested for make equivalent");
			retval = -1;
		} else {
			char *mp, *sub;
			retval = equiv_parse(optarg, &mp, &sub);
			if (retval < 0) {
				audit_msg(LOG_ERR, 
			   "Error parsing equivalent parts");
				retval = -1;
			} else {
				retval = audit_make_equivalent(fd, mp, sub);
				if (retval <= 0) {
					retval = -1;
				} else
					return -2; // success - no reply needed
			}
		}
		break;
        case 't':
		retval = audit_trim_subtrees(fd);
		if (retval <= 0)
			retval = -1;
		else
			return -2;  // success - no reply for this
		break;
	case 'v':
		printf("auditctl version %s\n", VERSION);
		retval = -2;
		break;
	// Now the long options
	case 1:
		retval = audit_set_loginuid_immutable(fd);
		if (retval <= 0)
			retval = -1;
		else
			return -2;  // success - no reply for this
		break;
	case 2:
#if HAVE_DECL_AUDIT_VERSION_BACKLOG_WAIT_TIME == 1 || \
    HAVE_DECL_AUDIT_STATUS_BACKLOG_WAIT_TIME == 1
		if (optarg && isdigit(optarg[0])) {
			uint32_t bwt;
			errno = 0;
			bwt = strtoul(optarg,NULL,0);
			if (errno) {
				audit_msg(LOG_ERR,
					"Error converting backlog_wait_time");
				return -1;
			}
			if (audit_set_backlog_wait_time(fd, bwt) > 0)
				audit_request_status(fd);
			else
				return -1;
		} else {
			audit_msg(LOG_ERR, 
			    "Backlog_wait_time must be a numeric value was %s", 
				optarg);
			retval = -1;
		}
#else
		audit_msg(LOG_ERR,
			"backlog_wait_time is not supported on your kernel");
		retval = -1;
#endif
		break;
	case 3:
		if ((rc = audit_reset_lost(fd)) >= 0) {
			audit_msg(LOG_INFO, "lost: %u", rc);
			return -2;
		} else {
			audit_number_to_errmsg(rc, long_opts[lidx].name);
			retval = -1;
		}
		break;
	case 4:
#if HAVE_DECL_AUDIT_STATUS_BACKLOG_WAIT_TIME_ACTUAL == 1
		if ((rc = audit_reset_backlog_wait_time_actual(fd)) >= 0) {
			audit_msg(LOG_INFO, "backlog_wait_time_actual: %u", rc);
			return -2;
		} else {
			audit_number_to_errmsg(rc, long_opts[lidx].name);
			retval = -1;
		}
#else
		audit_msg(LOG_ERR,
			"reset_backlog_wait_time_actual is not supported on your kernel");
		retval = -1;
#endif
		break;
	case 5:
		retval = send_signal(optarg);
		break;
        default: {
		char *bad_opt;
		if (optind >= 2)
			bad_opt = vars[optind -1];
		else
			bad_opt = " ";
		if (lineno)
			audit_msg(LOG_ERR,
			    "Option %s on line %d is invalid", bad_opt, lineno);
		else
			audit_msg(LOG_ERR, "Option %s is invalid", bad_opt);
		retval = -1;
		}
		break;
        }
    }
    /* catch extra args or errors where the user types "- s" */
    if (optind == 1)
	retval = -1;
    else if ((optind < count) && (retval != -1)) {
	audit_msg(LOG_ERR, "parameter passed without an option given");
	retval = -1;
    }

    /* See if we were adding a key */
    if (key[0] && list_requested == 0) {
	int flags = 0;
	char *cmd=NULL;

	/* Get the flag */
	if (add != AUDIT_FILTER_UNSET)
		flags = add & AUDIT_FILTER_MASK;
	else if (del != AUDIT_FILTER_UNSET)
		flags = del & AUDIT_FILTER_MASK;

	/* Build the command */
	if (asprintf(&cmd, "key=%s", key) < 0) {
		cmd = NULL;
		audit_msg(LOG_ERR, "Out of memory adding key");
		retval = -1;
	} else {
		/* Add this to the rule */
		int ret = audit_rule_fieldpair_data(&rule_new, cmd, flags);
		if (ret != 0) {
			audit_number_to_errmsg(ret, cmd);
			retval = -1;
		}
		free(cmd);
	}
    }
    if (retval == -1 && errno == ECONNREFUSED)
		audit_msg(LOG_ERR, "The audit system is disabled");
    return retval;
}

static char *get_line(FILE *f, char *buf)
{
	if (fgets_unlocked(buf, LINE_SIZE, f)) {
		/* remove newline */
		char *ptr = strchr(buf, 0x0a);
		if (ptr)
			*ptr = 0;
		return buf;
	}
	return NULL;
}


static void preprocess(char *buf)
{
	unsigned int i = 0;
	bool esc_ctx = false;

	while (buf[i]) {
		if (buf[i] == '\\' && esc_ctx == false)
			esc_ctx = true;
		else {
			if (esc_ctx == true) {
				if (buf[i] == ' ') {
					buf[i] = 0x07;
					buf[i - 1] = 0x07;
				} else if (buf[i] == '\\') {
					buf[i] = 0x04;
					buf[i - 1] = 0x04;
				}

				esc_ctx = false;
			}
		}

		i++;
	}
}


void postprocess(char *buf)
{
	char *str = strdup(buf);
	char *pos1 = str;
	char *pos2 = buf;

	if (!str)
		return;
    
	while (*pos1) {
		if (*pos1 == 0x07) {
			*pos2 = ' ';
			pos1 += 2;
			pos2++;
			continue;
		} else if (*pos1 == 0x04) {
			*pos2 = '\\';
			pos1 += 2;
			pos2++;
			continue;
		}

		*pos2 = *pos1;
		pos2++;
		pos1++;
	}

	*pos2 = 0;
	free(str);
}


/*
 * This function reads the given file line by line and executes the rule.
 * It returns 0 if everything went OK, 1 if there are problems before reading
 * the file, 2 if the rules file doesn't exist and it should,  and -1 on
 * error conditions after executing some of the rules. It will abort reading
 * the file if it encounters any problems.
 */
static int fileopt(const char *file)
{
	int i, tfd, rc, lineno = 1;
	struct stat st;
        FILE *f;
        char buf[LINE_SIZE];

	/* Does the file exist? */
	rc = open(file, O_RDONLY);
	if (rc < 0) {
		if (errno != ENOENT) {
			audit_msg(LOG_ERR,"Error opening %s (%s)", 
				file, strerror(errno));
                        return 1;
                }
		audit_msg(LOG_ERR, "audit rules file %s doesn't exist", file);
                return 2;
        }
        tfd = rc;

	/* Is the file permissions sane? */
	if (fstat(tfd, &st) < 0) {
		audit_msg(LOG_ERR, "Error fstat'ing %s (%s)",
			file, strerror(errno));
		close(tfd);
		return 1;
	}
	if (st.st_uid != 0) {
		audit_msg(LOG_ERR, "Error - %s isn't owned by root", file);
		close(tfd);
		return 1;
	} 
	if ((st.st_mode & S_IWOTH) == S_IWOTH) {
		audit_msg(LOG_ERR, "Error - %s is world writable", file);
		close(tfd);
		return 1;
	}
	if (!S_ISREG(st.st_mode)) {
		audit_msg(LOG_ERR, "Error - %s is not a regular file", file);
		close(tfd);
		return 1;
	}

	f = fdopen(tfd, "rm");
	if (f == NULL) {
		audit_msg(LOG_ERR, "Error - fdopen failed (%s)",
			strerror(errno));
		close(tfd);
		return 1;
	}

	/* Read until eof, lineno starts as 1 */
	while (get_line(f, buf)) {
		char *ptr, **fields;
		unsigned int idx=0, nf = (strlen(buf)/3) + 3;

		/* Weed out blank lines */
		while (buf[idx] == ' ')
			idx++;
		if (buf[idx] == 0) {
			lineno++;
			continue;
		}

		preprocess(buf);
		ptr = audit_strsplit(buf);
		if (ptr == NULL)
			break;

		/* allow comments */
		if (ptr[0] == '#') {
			lineno++;
			continue;
		}
		i = 0;
		fields = malloc(nf * sizeof(char *));
		fields[i++] = "auditctl";
		fields[i++] = ptr;
		while( (ptr=audit_strsplit(NULL)) && (i < nf-1)) {
		        postprocess(ptr);
			fields[i++] = ptr;
		}

		fields[i] = NULL;

		/* Parse it */
		if (reset_vars()) {
			free(fields);
			fclose(f);
			return -1;
		}
		rc = setopt(i, lineno, fields);
		free(fields);

		/* handle reply or send rule */
		if (rc != -3) {
			if (handle_request(rc) == -1) {
				if (errno != ECONNREFUSED)
					audit_msg(LOG_ERR,
					"There was an error in line %d of %s",
					lineno, file);
				else {
					audit_msg(LOG_ERR,
						"The audit system is disabled");
					fclose(f);
					return 0;
				}
				if (ignore == 0) {
					fclose(f);
					return -1;
				}
				if (continue_error)
					continue_error = -1;
			}
		}
		lineno++;
	}
	fclose(f);
	return 0;
}

/* Return 1 if ready, 0 otherwise */
static int is_ready(void)
{
	if (audit_is_enabled(fd) == 2) {
		audit_msg(LOG_ERR, "The audit system is in immutable mode,"
			" no rule changes allowed");
		return 0;
	} else if (errno == ECONNREFUSED) {
		audit_msg(LOG_ERR, "The audit system is disabled");
		return 0;
	}
	return 1;
}

int main(int argc, char *argv[])
{
	int retval = 1;

	set_aumessage_mode(MSG_STDERR, DBG_NO);

	if (argc == 1) {
		usage();
		return 1;
	}
#ifndef DEBUG
	/* Make sure we are root if we do anything except help */
	if (!(argc == 2 && (strcmp(argv[1], "--help")==0 ||
			strcmp(argv[1], "-h") == 0 ||
			(strcmp(argv[1], "-l") == 0 && geteuid() == 0))) &&
			!audit_can_control()) {
		audit_msg(LOG_WARNING, "You must be root to run this program.");
		return 4;
	}
#endif
	/* Check where the rules are coming from: commandline or file */
	if ((argc == 3) && (strcmp(argv[1], "-R") == 0)) {
		// If reading a file, its most likely start up. Send problems
		// to syslog where they will persist for later review
		set_aumessage_mode(MSG_SYSLOG, DBG_NO);
		fd = audit_open();
		if (is_ready() == 0)
			return 0;
		else if (fileopt(argv[2])) {
			free(rule_new);
			return 1;
		} else {
			free(rule_new);
			if (continue_error < 0)
				return 1;
			return 0;
		}
	} else {
		if (reset_vars()) {
			free(rule_new);
			return 1;
		}
		retval = setopt(argc, 0, argv);
		if (retval == -3) {
			free(rule_new);
			return 0;
		}
	}

	if (add != AUDIT_FILTER_UNSET || del != AUDIT_FILTER_UNSET) {
		fd = audit_open();
		if (is_ready() == 0) {
			free(rule_new);
			return 0;
		}
	}
	retval = handle_request(retval);
	free(rule_new);
	return retval;
}

/*
 * This function is called after setopt to handle the return code.
 * On entry, status = 0 means just get the reply. Greater than 0 means we
 * are adding or deleting a rule or watch. -1 means an error occurred.
 * -2 means everything is OK and no reply needed. Even if there's an 
 * error, we need to call this routine to close up the audit fd.
 * The return code from this function is 0 success and -1 error.
 */
static int handle_request(int status)
{
	if (status == 0) {
		if (_audit_syscalladded) {
			audit_msg(LOG_ERR, "Error - no list specified");
			return -1;
		}
		get_reply();
	} else if (status == -2)
		status = 0;  // report success 
	else if (status > 0) {
		int rc;
		if (add != AUDIT_FILTER_UNSET) {
			// if !task add syscall any if not specified
			if ((add & AUDIT_FILTER_MASK) != AUDIT_FILTER_TASK && 
					_audit_syscalladded != 1) {
					audit_rule_syscallbyname_data(
							rule_new, "all");
			}
			set_aumessage_mode(MSG_QUIET, DBG_NO);
			rc = audit_add_rule_data(fd, rule_new, add, action);
			set_aumessage_mode(MSG_STDERR, DBG_NO);
			/* Retry for legacy kernels */
			if (rc < 0) {
				if (errno == EINVAL &&
				rule_new->fields[0] == AUDIT_DIR) {
					rule_new->fields[0] = AUDIT_WATCH;
					rc = audit_add_rule_data(fd, rule_new,
							add, action);
				} else {
					audit_msg(LOG_ERR,
				"Error sending add rule data request (%s)",
					errno == EEXIST ?
					"Rule exists" : strerror(-rc));
				}
			}
		}
		else if (del != AUDIT_FILTER_UNSET) {
			if ((del & AUDIT_FILTER_MASK) != AUDIT_FILTER_TASK && 
					_audit_syscalladded != 1) {
					audit_rule_syscallbyname_data(
							rule_new, "all");
			}
			set_aumessage_mode(MSG_QUIET, DBG_NO);
			rc = audit_delete_rule_data(fd, rule_new,
								 del, action);
			set_aumessage_mode(MSG_STDERR, DBG_NO);
			/* Retry for legacy kernels */
			if (rc < 0) {
				if (errno == EINVAL &&
					rule_new->fields[0] == AUDIT_DIR) {
					rule_new->fields[0] = AUDIT_WATCH;
					rc = audit_delete_rule_data(fd,rule_new,
								del, action);
				} else {
					audit_msg(LOG_ERR,
			       "Error sending delete rule data request (%s)",
					errno == EEXIST ?
					"Rule exists" : strerror(-rc));
				}
			}
		} else {
			usage();
			audit_close(fd);
			exit(1);
		}
		if (rc <= 0)
			status = -1;
		else
			status = 0;
	} else
		status = -1;

	if (!list_requested)
		audit_close(fd);
	fd = -1;
	return status;
}

/*
 * A reply from the kernel is expected. Get and display it.
 */
static void get_reply(void)
{
	int i, retval;
	int timeout = 40; /* loop has delay of .1 - so this is 4 seconds */
	struct audit_reply rep;
	fd_set read_mask;
	FD_ZERO(&read_mask);
	FD_SET(fd, &read_mask);

	// Reset printing counter
	audit_print_init();

	for (i = 0; i < timeout; i++) {
		struct timeval t;

		t.tv_sec  = 0;
		t.tv_usec = 100000; /* .1 second */
		do {
			retval=select(fd+1, &read_mask, NULL, NULL, &t);
		} while (retval < 0 && errno == EINTR);
		// We'll try to read just in case
		retval = audit_get_reply(fd, &rep, GET_REPLY_NONBLOCKING, 0);
		if (retval > 0) {
			if (rep.type == NLMSG_ERROR && rep.error->error == 0) {
				i = 0;    /* reset timeout */
				continue; /* This was an ack */
			}

			if ((retval = audit_print_reply(&rep, fd)) == 0)
				break;
			else
				i = 0; /* If getting more, reset timeout */
		}
	}
}

