/*
 * aureport.c - main file for aureport utility
 * Copyright 2005-08, 2010,11,2013,2020 Red Hat
 * 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 <stdio_ext.h>
#include <string.h>
#include <stdlib.h>
#include <getopt.h>
#include <unistd.h>
#include <ctype.h>
#include <time.h>
#include <errno.h>
#include <sys/resource.h>
#include <sys/stat.h>
#include <locale.h>
#include <sys/param.h>
#include "libaudit.h"
#include "auditd-config.h"
#include "aureport-options.h"
#include "aureport-scan.h"
#include "ausearch-lol.h"
#include "ausearch-lookup.h"
#include "auparse-idata.h"
#include "ausearch-parse.h"
#include "common.h"


extern event very_first_event;
event very_last_event;
static auparse_state_t *au = NULL;
static FILE *log_fd = NULL;
static lol lo;
static int found = 0;
static int files_to_process = 0; // Logs left when processing multiple
static int userfile_is_dir = 0;
static struct daemon_conf config;
static int process_logs(void);
static int process_log_fd(const char *filename);
static int process_stdin(void);
static int process_file(char *filename);
static int get_event(llist **);

extern char *user_file;
extern int force_logs;

/*
 * User space configuration items
 */
extern time_t arg_eoe_timeout;


static int is_pipe(int fd)
{
	struct stat st;

	if (fstat(fd, &st) == 0) {
		if (S_ISFIFO(st.st_mode))
			return 1;
	}
	return 0;
}

int main(int argc, char *argv[])
{
	struct rlimit limit;
	int rc;

	/* Check params and build regexpr */
	setlocale (LC_ALL, "");
	if (check_params(argc, argv))
		return 1;

	/* Raise the rlimits in case we're being started from a shell
	* with restrictions. Not a fatal error.  */
	limit.rlim_cur = RLIM_INFINITY;
	limit.rlim_max = RLIM_INFINITY;
	setrlimit(RLIMIT_FSIZE, &limit);
	setrlimit(RLIMIT_CPU, &limit);
	_set_aumessage_mode(MSG_STDERR, DBG_NO);
	set_allow_links(1); // auditd might have -l flag, aureport should be lenient here
	(void) umask( umask( 077 ) | 027 );
	very_first_event.sec = 0;
	au = auparse_init(AUSOURCE_BUFFER, "");
	if (au == NULL) {
		fprintf(stderr, "cannot init parser\n");
		return 1;
	}
	reset_counters();

	/* Load config so we know where logs are and eoe_timeout */
        if (load_config(&config, TEST_SEARCH))
		fprintf(stderr, "NOTE - using built-in logs: %s\n",
				config.log_file);

	/* Set timeout from the config file */
	lol_set_eoe_timeout((time_t)config.end_of_event_timeout);

	/*
	 * If an override was specified on the command line, override the config
	 */
	if (arg_eoe_timeout != 0)
		lol_set_eoe_timeout((time_t)arg_eoe_timeout);

	print_title();
	if (arg_eoe_timeout != 0) {
		lol_set_eoe_timeout(arg_eoe_timeout);
	}

	lol_create(&lo);
	if (user_file) {
		struct stat sb;
		if (stat(user_file, &sb) == -1) {
			perror("stat");
			return 1;
		} else {
			switch (sb.st_mode & S_IFMT) {
				case S_IFDIR: 
					userfile_is_dir = 1;
					rc = process_logs();
					break;
				case S_IFREG:
				default:
					rc = process_file(user_file);
					break;
			}
		}
	} else if (force_logs)
		rc = process_logs();
	else if (is_pipe(0))
		rc = process_stdin();
	else
		rc = process_logs();
	lol_clear(&lo);
	if (rc)
		return rc;

	if (!found && report_detail == D_DETAILED && report_type != RPT_TIME) {
		printf("<no events of interest were found>\n\n");
		destroy_counters();
		aulookup_destroy_uid_list();
		return 1;
	} else
		print_wrap_up();
	destroy_counters();
	aulookup_destroy_uid_list();
	lookup_uid_destroy_list();
	free(user_file);
	return 0;
}

static int process_logs(void)
{
	struct audit_log_info *logs = NULL;
	char *filename;
	size_t len;
	size_t log_cnt = 0;

	if (user_file && userfile_is_dir) {
		char dirname[MAXPATHLEN+1];
		clear_config (&config);

		strncpy(dirname, user_file, MAXPATHLEN-32);
		dirname[MAXPATHLEN-32] = '\0';
		if (dirname[strlen(dirname)-1] != '/')
			strcat(dirname, "/");
		strcat (dirname, "audit.log");
		free((void *)config.log_file);
		config.log_file=strdup(dirname);
		fprintf(stderr, "NOTE - using logs in %s\n", config.log_file);
	}

	len = strlen(config.log_file) + 16;
	filename = malloc(len);
	if (!filename) {
		fprintf(stderr, "No memory\n");
		free_config(&config);
		return 1;
	}

	/* Count the logs */
	if (audit_log_list(config.log_file, &logs, &log_cnt)) {
		fprintf(stderr, "No memory\n");
		free(filename);
		free_config(&config);
		return 1;
	}

	if (log_cnt == 0) {
		snprintf(filename, len, "%s", config.log_file);
		int ret = process_file(filename);
		free(filename);
		free_config(&config);
		return ret;
	}

	/* Locate the starting file that is in range */
	files_to_process = audit_log_find_start(logs, log_cnt, start_time);
	audit_log_free(logs, log_cnt);

	/* Got it, now process logs from last to first */
	if (files_to_process > 0)
		snprintf(filename, len, "%s.%d", config.log_file,
			 files_to_process);
	else
		snprintf(filename, len, "%s", config.log_file);
	do {
		int ret;
		if ((ret = process_file(filename))) {
			free(filename);
			free_config(&config);
			return ret;
		}

		if (files_to_process == 0)
			break;

		/* Get next log file */
		files_to_process--;
		if (files_to_process > 0)
			snprintf(filename, len, "%s.%d",
				 config.log_file, files_to_process);
		else
			snprintf(filename, len, "%s",
				 config.log_file);
	} while (1);
	free(filename);
	free_config(&config);
	return 0;
}

static void process_event(llist *entries)
{
	if (scan(entries)) {
		// If its a single event or SYSCALL load interpretations
		if ((entries->cnt == 1) ||
				(entries->head->type == AUDIT_SYSCALL)) {
			_auparse_load_interpretations(au,
					entries->head->interp);
		}
		// This is the per entry action item
		if (per_event_processing(entries))
			found = 1;
		_auparse_free_interpretations(au);
	}
}

static int process_log_fd(const char *filename)
{
	llist *entries; // entries in a record
	int ret;
	int first = 0;
	event first_event, last_event;

	last_event.sec = 0;
	last_event.milli = 0;

	/* For each event in file */
	do {
		ret = get_event(&entries);
		if ((ret != 0)||(entries->cnt == 0)||(entries->head == NULL))
			break;
		// If report is RPT_TIME or RPT_SUMMARY, get 
		if (report_type <= RPT_SUMMARY) {
			if (first == 0) {
				list_get_event(entries, &first_event);
				first = 1;
			}
			list_get_event(entries, &last_event);
		}
		// Are we within time range?
		if (start_time == 0 || entries->e.sec >= start_time) {
			if (end_time == 0 || entries->e.sec <= end_time) {
				process_event(entries);
			}
		}
		list_clear(entries);
		free(entries);
	} while (ret == 0);
	fclose(log_fd);
	// This is the per file action items
	very_last_event.sec = last_event.sec;
	very_last_event.milli = last_event.milli;
	if (report_type == RPT_TIME) {
		if (first == 0) {
			printf("%s: no records\n", filename);
		} else {
			struct tm *btm;
			char tmp[32];

			printf("%s: ", filename);
			btm = localtime(&first_event.sec);
			if (btm)
				strftime(tmp, sizeof(tmp), "%x %T", btm);
			else
				strcpy(tmp, "?");
			printf("%s.%03u - ", tmp, first_event.milli);
			btm = localtime(&last_event.sec);
			if (btm)
				strftime(tmp, sizeof(tmp), "%x %T", btm);
			else
				strcpy(tmp, "?");
			printf("%s.%03u\n", tmp, last_event.milli);
		}
	}

	return 0;
}

static int process_stdin(void)
{
	log_fd = stdin;

	return process_log_fd("stdin");
}

static int process_file(char *filename)
{
	log_fd = fopen(filename, "rm");
	if (log_fd == NULL) {
		fprintf(stderr, "Error opening %s (%s)\n", filename, 
			strerror(errno));
		return 1;
	}

	__fsetlocking(log_fd, FSETLOCKING_BYCALLER);
	return process_log_fd(filename);
}

/*
 * This function returns a linked list of all records in an event.
 * It returns 0 on success, 1 on eof, -1 on error. 
 */
static int get_event(llist **l)
{
	char *rc;
	char *buff = NULL;

	*l = get_ready_event(&lo);
	if (*l)
		return 0;

	while (1) {
		if (!buff) {
			buff = malloc(MAX_AUDIT_MESSAGE_LENGTH);
			if (!buff)
				return -1;
		}
		rc = fgets_unlocked(buff, MAX_AUDIT_MESSAGE_LENGTH,
					log_fd);
		if (rc) {
			if (lol_add_record(&lo, buff)) {
				*l = get_ready_event(&lo);
				if (*l)
					break;
			}
		} else {
			free(buff);
			if (feof_unlocked(log_fd)) {
				// Only mark all events complete if this is
				// the last file.
				if (files_to_process == 0) {
					terminate_all_events(&lo);
				}
				*l = get_ready_event(&lo);
				if (*l)
					return 0;
				else
					return 1;
			} else 
				return -1;
		}
	}
	free(buff);
	return 0;
}

