/* autrace.c -- 
 * Copyright 2005-09,2011,2015-16 Red Hat Inc., Durham, North Carolina.
 * 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>
 */

#include "config.h"
#include <stdio.h>
#include <string.h>
#include <sys/wait.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include <errno.h>
#include <time.h>
#include <linux/net.h>
#include "libaudit.h"
#include "private.h"

/*
 * This program will add the audit rules to trace a process similar
 * to strace. It will then execute the process.
 */
static int threat = 0;
static int count_rules(void);
static int count_em(int fd);
extern int delete_all_rules(int fd);

static void usage(void)
{
	fprintf(stderr, "usage: autrace [-r] program\n");
}

static int insert_rule(int audit_fd, const char *field)
{
	int rc;
	int flags = AUDIT_FILTER_EXIT;
	int action = AUDIT_ALWAYS;
	struct audit_rule_data *rule = audit_rule_create_data();
	int machine = audit_detect_machine();
	char *t_field = NULL;

	if (rule == NULL)
		goto err;
	if (threat) {
		rc = 0;
		if (machine != MACH_AARCH64) {
			rc |= audit_rule_syscallbyname_data(rule, "open");
			rc |= audit_rule_syscallbyname_data(rule, "creat");
			rc |= audit_rule_syscallbyname_data(rule, "rename");
			rc |= audit_rule_syscallbyname_data(rule, "unlink");
			rc |= audit_rule_syscallbyname_data(rule, "mknod");
			rc |= audit_rule_syscallbyname_data(rule, "mkdir");
			rc |= audit_rule_syscallbyname_data(rule, "rmdir");
			rc |= audit_rule_syscallbyname_data(rule, "chown");
			rc |= audit_rule_syscallbyname_data(rule, "lchown");
			rc |= audit_rule_syscallbyname_data(rule, "chmod");
			rc |= audit_rule_syscallbyname_data(rule, "link");
			rc |= audit_rule_syscallbyname_data(rule, "symlink");
			rc |= audit_rule_syscallbyname_data(rule, "readlink");
		}
		rc |= audit_rule_syscallbyname_data(rule, "openat");
		rc |= audit_rule_syscallbyname_data(rule, "truncate");
		rc |= audit_rule_syscallbyname_data(rule, "renameat");
		rc |= audit_rule_syscallbyname_data(rule, "unlinkat");
		rc |= audit_rule_syscallbyname_data(rule, "mknodat");
		rc |= audit_rule_syscallbyname_data(rule, "mkdirat");
		rc |= audit_rule_syscallbyname_data(rule, "chdir");
		rc |= audit_rule_syscallbyname_data(rule, "fchownat");
		rc |= audit_rule_syscallbyname_data(rule, "fchmodat");
		rc |= audit_rule_syscallbyname_data(rule, "linkat");
		rc |= audit_rule_syscallbyname_data(rule, "symlinkat");
		rc |= audit_rule_syscallbyname_data(rule, "readlinkat");
		rc |= audit_rule_syscallbyname_data(rule, "execve");
		rc |= audit_rule_syscallbyname_data(rule, "name_to_handle_at");
		rc |= audit_rule_syscallbyname_data(rule, "openat2");

		if (machine != MACH_X86 && machine != MACH_S390X && 
						machine != MACH_S390) {
			rc |= audit_rule_syscallbyname_data(rule, "connect");
			rc |= audit_rule_syscallbyname_data(rule, "bind");
			rc |= audit_rule_syscallbyname_data(rule, "accept");
			rc |= audit_rule_syscallbyname_data(rule, "sendto");
			rc |= audit_rule_syscallbyname_data(rule, "recvfrom");
			rc |= audit_rule_syscallbyname_data(rule, "accept4");
		}

		rc |= audit_rule_syscallbyname_data(rule, "sendfile");
	} else
		rc = audit_rule_syscallbyname_data(rule, "all");
	if (rc < 0)
		goto err;
	t_field = strdup(field);
	rc = audit_rule_fieldpair_data(&rule, t_field, flags);
	free(t_field);
	if (rc < 0)
		goto err;
	rc = audit_add_rule_data(audit_fd, rule, flags, action);
	if (rc < 0)
		goto err;

	// Now if i386, lets add its network rules
	if (machine == MACH_X86 || machine == MACH_S390X ||
						machine == MACH_S390) {
		int i, a0[6] = { SYS_CONNECT, SYS_BIND, SYS_ACCEPT, SYS_SENDTO,
				 SYS_RECVFROM, SYS_ACCEPT4 };
		for (i=0; i<6; i++) {
			char pair[32];

			memset(rule, 0, sizeof(struct audit_rule_data));
			rc |= audit_rule_syscallbyname_data(rule, "socketcall");
			snprintf(pair, sizeof(pair), "a0=%d", a0[i]);
			rc |= audit_rule_fieldpair_data(&rule, pair, flags);
			t_field = strdup(field);
			rc |= audit_rule_fieldpair_data(&rule, t_field, flags);
			free(t_field);
			rc |= audit_add_rule_data(audit_fd, rule, flags, action);
		}
	}
	free(rule);
	return 0;
err:
	fprintf(stderr, "Error inserting audit rule for %s\n", field);
	free(rule);
	return 1;
}

int key_match(struct audit_reply *rep)
{
	return 1;
}

/*
 * Algorithm:
 * check that user is root
 * check to see if program exists
 * if so fork, child waits for parent
 * parent clears audit rules, loads audit all syscalls with child's pid
 * parent tells child to go & waits for sigchld
 * child exec's program
 * parent deletes rules after getting sigchld
 */
int main(int argc, char *argv[])
{
	int fd[2];
	int pid,cmd=1;
	char buf[2];

	if (argc < 2) {
		usage();
		return 1;
	}
	if (strcmp(argv[cmd], "-h") == 0) {
		usage();
		return 1;
	}
	if (strcmp(argv[cmd], "-r") == 0) {
		threat = 1;
		cmd++;
	}
	if (!audit_can_control()) {
		fprintf(stderr,
		"You must be root or have capabilities to run this program.\n");
		return 1;
	}
	if (access(argv[cmd], X_OK)) {
		if (errno == ENOENT)
			fprintf(stderr, "Error - can't find: %s\n", argv[cmd]); 
		else
			fprintf(stderr, "Error checking %s (%s)\n", 
				argv[cmd], strerror(errno));
		return 1;
	}
	set_aumessage_mode(MSG_STDERR, DBG_NO);
	switch (count_rules())
	{
		case -1:
			if (errno == ECONNREFUSED)
		                fprintf(stderr,
					"The audit system is disabled\n");
			else
				fprintf(stderr,
					"Error - can't get rule count.\n");
			return 1;
		case 0:
			break;
		default:
			fprintf(stderr, 
			"autrace cannot be run with rules loaded.\n"
			"Please delete all rules using 'auditctl -D' if you "
			"really wanted to\nrun this command.\n");
			return 1;
	}
	if (pipe(fd) != 0) {
		fprintf(stderr, "Error creating pipe.\n");
		return 1;
	}
	
	switch ((pid=fork()))
	{
		case -1:
			fprintf(stderr, "Error forking.\n");
			return 1;
		case 0: /* Child */
			close(fd[1]);
			printf("Waiting to execute: %s\n", argv[cmd]);
			while (read(fd[0], buf, 1) == -1 && errno == EINTR)
				/* blank */ ;
			close(fd[0]);
			execvp(argv[cmd], &argv[cmd]);
			fprintf(stderr, "Failed to exec %s\n", argv[cmd]);
			return 1;
		default: /* Parent */
			close(fd[0]);
			fcntl(fd[1], F_SETFD, FD_CLOEXEC);
			{
				char field[16];
				int audit_fd;
  				audit_fd = audit_open();
				if (audit_fd < 0)
					exit(1);
				snprintf(field, sizeof(field), "pid=%d", pid);
				if (insert_rule(audit_fd, field)) {
					kill(pid,SIGTERM);
					(void)delete_all_rules(audit_fd);
					exit(1);
				}
				snprintf(field, sizeof(field), "ppid=%d", pid);
				if (insert_rule(audit_fd, field)) {
					kill(pid,SIGTERM);
					(void)delete_all_rules(audit_fd);
					exit(1);
				}
				sleep(1);
				if (write(fd[1],"1", 1) != 1) {
					kill(pid,SIGTERM);
					(void)delete_all_rules(audit_fd);
					exit(1);
				}
				waitpid(pid, NULL, 0);
				close(fd[1]);
				puts("Cleaning up...");
				(void)delete_all_rules(audit_fd);
				close(audit_fd);
			}
			printf("Trace complete. "
				"You can locate the records with "
				"\'ausearch -i -p %d\'\n",
				pid);
			break;
	}

	return 0;
}

static int count_rules(void)
{
	int fd, total, rc;

	fd = audit_open();
	if (fd < 0) 
		return -1;

	rc = audit_request_rules_list_data(fd);
	if (rc > 0) 
		total = count_em(fd);
	else 
		total = -1;

	close(fd); 
	return total;
}

static int count_em(int fd)
{
	int i, retval, count = 0;
	int timeout = 40; /* loop has delay of .1 - this is 4 seconds */
	struct audit_reply rep;
	fd_set read_mask;

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

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

		t.tv_sec  = 0;
		t.tv_usec = 100000; /* .1 second */
		retval = audit_get_reply(fd, &rep, GET_REPLY_NONBLOCKING, 0);
		if (retval > 0) {
			if (rep.type == NLMSG_ERROR && 
					rep.error->error == 0)
				continue;
			do {
				retval=select(fd+1, &read_mask, NULL, NULL, &t);
			} while (retval < 0 && errno == EINTR);

			switch (rep.type)
			{
				case NLMSG_DONE:
					return count;
				case AUDIT_LIST_RULES:
					i = 0;
					count++;
					break;
				case NLMSG_ERROR:
					return -1;
				default:
					break;
			}
		} else if (errno == EAGAIN)  // Take short delay
			select(fd+1, &read_mask, NULL, NULL, &t);
	}
	if (i >= timeout && count == 0)
		count = -1;
	return count;
}

