/*
* ausearch-lookup.c - Lookup values to something more readable
* Copyright (c) 2005-06,2011-12,2015-17 Red Hat Inc.
* All Rights Reserved. 
*
* This software may be freely redistributed and/or modified under the
* terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2, 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; see the file COPYING. If not, write to the
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor 
* Boston, MA 02110-1335, USA.
*
* Authors:
*   Steve Grubb <sgrubb@redhat.com>
*/

#include "config.h"
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <stdlib.h>
#include <linux/net.h>
#include "ausearch-lookup.h"
#include "ausearch-options.h"
#include "ausearch-nvpair.h"
#include "auparse-idata.h"

static int interp_init = 0;
static auparse_state_t *au = NULL;

/* This is the name/value pair used by search tables */
struct nv_pair {
	int        value;
	const char *name;
};


/* The machine based on elf type */
static int machine = 0;
static const char *Q = "?";
static const char *results[3]= { "unset", "denied", "granted" };
static const char *success[3]= { "unset", "no", "yes" };
static const char *aulookup_socketcall(long sc);
static const char *aulookup_ipccall(long ic);

const char *aulookup_result(avc_t result)
{
	return results[result];
}

const char *aulookup_success(int s)
{
	switch (s)
	{
		default:
			return success[0];
			break;
		case S_FAILED:
			return success[1];
			break;
		case S_SUCCESS:
			return success[2];
			break;
	}
}

const char *aulookup_syscall(llist *l, char *buf, size_t size)
{
	const char *sys;

	if (!interp_init) {
		au = auparse_init(AUSOURCE_BUFFER, "");
		if (au == NULL)
			return NULL;
		interp_init = 1;
	}

	if (report_format <= RPT_DEFAULT) {
		snprintf(buf, size, "%d", l->s.syscall);
		return buf;
	}

	sys = _auparse_lookup_interpretation(au, "syscall");
	if (sys) {
		snprintf(buf, size, "%s", sys);
		free((void *)sys);
		return buf;
	}

	machine = audit_elf_to_machine(l->s.arch);
	if (machine < 0)
		return Q;
	sys = audit_syscall_to_name(l->s.syscall, machine);
	if (sys) {
		const char *func = NULL;
		if (strcmp(sys, "socketcall") == 0) {
			if (list_find_item(l, AUDIT_SYSCALL))
				func = aulookup_socketcall((long)l->cur->a0);
		} else if (strcmp(sys, "ipc") == 0) {
			if(list_find_item(l, AUDIT_SYSCALL))
				func = aulookup_ipccall((long)l->cur->a0);
		}
		if (func) {
			snprintf(buf, size, "%s(%s)", sys, func);
			return buf;
		}
		snprintf(buf, size, "%s", sys);
		return buf;
	}
	snprintf(buf, size, "%d", l->s.syscall);
	return buf;
}

// See include/linux/net.h
static const struct nv_pair socktab[] = {
	{SYS_SOCKET, "socket"},
	{SYS_BIND, "bind"},
	{SYS_CONNECT, "connect"},
	{SYS_LISTEN, "listen"},
	{SYS_ACCEPT, "accept"},
	{SYS_GETSOCKNAME, "getsockname"},
	{SYS_GETPEERNAME, "getpeername"},
	{SYS_SOCKETPAIR, "socketpair"},
	{SYS_SEND, "send"},
	{SYS_RECV, "recv"},
	{SYS_SENDTO, "sendto"},
	{SYS_RECVFROM, "recvfrom"},
	{SYS_SHUTDOWN, "shutdown"},
	{SYS_SETSOCKOPT, "setsockopt"},
	{SYS_GETSOCKOPT, "getsockopt"},
	{SYS_SENDMSG, "sendmsg"},
	{SYS_RECVMSG, "recvmsg"},
	{SYS_ACCEPT4, "accept4"},
	{19, "recvmmsg"},
	{20, "sendmmsg"}
};
#define SOCK_NAMES (sizeof(socktab)/sizeof(socktab[0]))

static const char *aulookup_socketcall(long sc)
{
        unsigned int i;

        for (i = 0; i < SOCK_NAMES; i++)
                if (socktab[i].value == sc)
                        return socktab[i].name;

        return NULL;
}

/* This is from asm/ipc.h. Copying it for now as some platforms
 * have broken headers. */
#define SEMOP            1
#define SEMGET           2
#define SEMCTL           3
#define SEMTIMEDOP	 4
#define MSGSND          11
#define MSGRCV          12
#define MSGGET          13
#define MSGCTL          14
#define SHMAT           21
#define SHMDT           22
#define SHMGET          23
#define SHMCTL          24

/*
 * This table maps ipc calls to their text name
 */
static const struct nv_pair ipctab[] = {
        {SEMOP, "semop"},
        {SEMGET, "semget"},
        {SEMCTL, "semctl"},
        {SEMTIMEDOP, "semtimedop"},
        {MSGSND, "msgsnd"},
        {MSGRCV, "msgrcv"},
        {MSGGET, "msgget"},
        {MSGCTL, "msgctl"},
        {SHMAT, "shmat"},
        {SHMDT, "shmdt"},
        {SHMGET, "shmget"},
        {SHMCTL, "shmctl"}
};
#define IPC_NAMES (sizeof(ipctab)/sizeof(ipctab[0]))

static const char *aulookup_ipccall(long ic)
{
        unsigned int i;

        for (i = 0; i < IPC_NAMES; i++)
                if (ipctab[i].value == ic)
                        return ipctab[i].name;

        return NULL;
} 

static nvlist uid_nvl;
static int uid_list_created=0;
const char *aulookup_uid(uid_t uid, char *buf, size_t size)
{
	const char *name;
	int rc;

	if (!interp_init) {
		au = auparse_init(AUSOURCE_BUFFER, "");
		if (au == NULL)
			return NULL;
		interp_init = 1;
	}

	if (report_format <= RPT_DEFAULT) {
		snprintf(buf, size, "%d", uid);
		return buf;
	}
	if (uid == -1) {
		snprintf(buf, size, "unset");
		return buf;
	}

	name = _auparse_lookup_interpretation(au, "auid");
	if (name) {
		snprintf(buf, size, "%s", name);
		free((void *)name);
		return buf;
	}

	// Check the cache first
	if (uid_list_created == 0) {
        search_list_create(&uid_nvl);
        search_list_clear(&uid_nvl);
		uid_list_created = 1;
	}
	rc = search_list_find_val(&uid_nvl, uid);
	if (rc) {
		name = uid_nvl.cur->name;
	} else {
		// This getpw use is OK because its for protocol 1 compatibility
		// Add it to cache
		struct passwd *pw;
		pw = getpwuid(uid);
		if (pw) {
			nvnode nv;
			nv.name = strdup(pw->pw_name);
			nv.val = uid;
            search_list_append(&uid_nvl, &nv);
			name = uid_nvl.cur->name;
		}
	}
	if (name != NULL)
		snprintf(buf, size, "%s", name);
	else
		snprintf(buf, size, "unknown(%d)", uid);
	return buf;
}

void aulookup_destroy_uid_list(void)
{
	if (uid_list_created == 0)
		return;

    search_list_clear(&uid_nvl);
	uid_list_created = 0;
}

static int is_hex_string(const char *str)
{
	while (*str) {
		if (!isxdigit(*str))
			return 0;
		str++;
	}
	return 1;
}
/*
 * This function will take a pointer to a 2 byte Ascii character buffer and 
 * return the actual hex value.
 */
static unsigned char x2c(unsigned char *buf)
{
	static const char AsciiArray[17] = "0123456789ABCDEF";
	char *ptr;
	unsigned char total=0;

	ptr = strchr(AsciiArray, (char)toupper(buf[0]));
	if (ptr)
		total = (unsigned char)(((ptr-AsciiArray) & 0x0F)<<4);
	ptr = strchr(AsciiArray, (char)toupper(buf[1]));
	if (ptr)
		total += (unsigned char)((ptr-AsciiArray) & 0x0F);

	return total;
}

/* returns a freshly malloc'ed and converted buffer */
char *unescape(const char *buf)
{
	int len, i;
	char *str, *strptr;
	const char *ptr = buf;

	/* Find the end of the name */
	if (*ptr == '(') {
		ptr = strchr(ptr, ')');
		if (ptr == NULL)
			return NULL;
		else
			ptr++;
	} else {
		while (isxdigit(*ptr))
			ptr++;
	}
	if ((ptr - buf) == 0)
		return NULL;

	str = strndup(buf, ptr - buf);
	if (str == NULL) {
		fprintf(stderr, "Out of memory. Check %s file, %d line", __FILE__, __LINE__);
		return NULL;
	}

	if (*buf == '(')
		return str;

	/* We can get away with this since the buffer is 2 times
	 * bigger than what we are putting there.
	 */
	len = strlen(str);
	if (len < 2) {
		free(str);
		return NULL;
	}
	strptr = str;
	for (i=0; i<len; i+=2) {
		*strptr = x2c((unsigned char *)&str[i]);
		strptr++;
	}
	*strptr = 0;
	return str;
}

static int need_tty_escape(const unsigned char *s, unsigned int len)
{
	unsigned int i = 0;
	while (i < len) {
		if (s[i] < 32)
			return 1;
		i++;
	}
	return 0;
}

static void tty_escape(const char *s, unsigned int len)
{
	unsigned int i = 0;
	while (i < len) {
		if ((unsigned char)s[i] < 32) {
			putchar('\\');
			putchar('0' + ((s[i] & 0300) >> 6));
			putchar('0' + ((s[i] & 0070) >> 3));
			putchar('0' + (s[i] & 0007));
		} else
			putchar(s[i]);
		i++;
	}
}

static const char sh_set[] = "\"'`$\\!()| ";
static unsigned int need_shell_escape(const char *s, unsigned int len)
{
	unsigned int i = 0, cnt = 0;
	while (i < len) {
		if (s[i] < 32)
			cnt++;
		else if (strchr(sh_set, s[i]))
			cnt++;
		i++;
	}
	return cnt;
}

static void shell_escape(const char *s, unsigned int len)
{
	unsigned int i = 0;
	while (i < len) {
		if ((unsigned char)s[i] < 32) {
			putchar('\\');
			putchar('0' + ((s[i] & 0300) >> 6));
			putchar('0' + ((s[i] & 0070) >> 3));
			putchar('0' + (s[i] & 0007));
		} else if (strchr(sh_set, s[i])) {
			putchar('\\');
			putchar(s[i]);
		} else
			putchar(s[i]);
		i++;
	}
}

static const char quote_set[] = "\"'`$\\!()| ;#&*?[]<>{}";
static unsigned int need_shell_quote_escape(const unsigned char *s, unsigned int len)
{
	unsigned int i = 0, cnt = 0;
	while (i < len) {
		if (s[i] < 32)
			cnt++;
		else if (strchr(quote_set, s[i]))
			cnt++;
		i++;
	}
	return cnt;
}

static void shell_quote_escape(const char *s, unsigned int len)
{
	unsigned int i = 0;
	while (i < len) {
		if ((unsigned char)s[i] < 32) {
			putchar('\\');
			putchar('0' + ((s[i] & 0300) >> 6));
			putchar('0' + ((s[i] & 0070) >> 3));
			putchar('0' + (s[i] & 0007));
		} else if (strchr(quote_set, s[i])) {
			putchar('\\');
			putchar(s[i]);
		} else
			putchar(s[i]);
		i++;
	}
}

static unsigned int need_escaping(const char *s, unsigned int len)
{
	switch (escape_mode)
	{
		case AUPARSE_ESC_RAW:
			break;
		case AUPARSE_ESC_TTY:
			return need_tty_escape(s, len);
		case AUPARSE_ESC_SHELL:
			return need_shell_escape(s, len);
		case AUPARSE_ESC_SHELL_QUOTE:
			return need_shell_quote_escape(s, len);
	}
	return 0;
}

static void escape(const char *s, unsigned int len)
{
	switch (escape_mode)
	{
		case AUPARSE_ESC_RAW:
			break;
		case AUPARSE_ESC_TTY:
			tty_escape(s, len);
			break;
		case AUPARSE_ESC_SHELL:
			shell_escape(s, len);
			break;
		case AUPARSE_ESC_SHELL_QUOTE:
			shell_quote_escape(s, len);
			break;
	}
}

void safe_print_string_n(const char *s, unsigned int len, int ret)
{
	if (len > MAX_AUDIT_MESSAGE_LENGTH)
		len = MAX_AUDIT_MESSAGE_LENGTH;

	if (need_escaping(s, len)) {
		escape(s, len);
		if (ret)
			putchar('\n');
	} else if (ret)
		puts(s);
	else
		printf("%s", s);
}

void safe_print_string(const char *s, int ret)
{
	if (s == NULL)
		fputs("(null)", stdout);
	else
		safe_print_string_n(s, strlen(s), ret);
}

/* Represent c as a character within a quoted string, and append it to buf. */
static void tty_printable_char(unsigned char c)
{
	if (c < 0x20 || c > 0x7E) {
		putchar('\\');
		putchar('0' + ((c >> 6) & 07));
		putchar('0' + ((c >> 3) & 07));
		putchar('0' + (c & 07));
	} else {
		if (c == '\\' || c ==  '"')
			putchar('\\');
		putchar(c);
	}
}

/* Search for a name of a sequence of TTY bytes.
 *  If found, return the name and advance *INPUT.
 *  Return NULL otherwise. 
 */
static const char *tty_find_named_key(unsigned char **input, size_t input_len)
{
	/* NUL-terminated list of (sequence, NUL, name, NUL) entries.
	   First match wins, even if a longer match were possible later */
	static const unsigned char named_keys[] =
#define E(SEQ, NAME) SEQ "\0" NAME "\0"
#include "auparse/tty_named_keys.h"
#undef E
	"\0";

	unsigned char *src;
	const unsigned char *nk;

	src = *input;
	if (*src >= ' ' && (*src < 0x7F || *src >= 0xA0))
		return NULL; /* Fast path */
	nk = named_keys;
	do {
		const unsigned char *p;
		size_t nk_len;

		p = strchr((const char *)nk, '\0');
		nk_len = p - nk;
		if (nk_len <= input_len && memcmp(src, nk, nk_len) == 0) {
			*input += nk_len;
			return (const char *)(p + 1);
		}
		nk = strchr((const char *)p + 1, '\0') + 1;
	} while (*nk != '\0');
	return NULL;
}

void print_tty_data(const char *val)
{
	int need_comma, in_printable = 0;
	unsigned char *data, *data_pos, *data_end;

	if (!is_hex_string(val)) {
		printf("%s", val);
		return;
	}

	if ((data = unescape((char *)val)) == NULL) {
		printf("conversion error(%s)", val);
		return;
	}

	data_end = data + strlen(val) / 2;
	data_pos = data;
	need_comma = 0;
	while (data_pos < data_end) {
		/* FIXME: Unicode */
		const char *desc;

		desc = tty_find_named_key(&data_pos, data_end - data_pos);
		if (desc != NULL) {
			if (in_printable != 0) {
				putchar('"');
				in_printable = 0;
			}
			if (need_comma != 0)
				putchar(',');
			printf("<%s>", desc);
		} else {
			if (in_printable == 0) {
				if (need_comma != 0)
					putchar(',');
				putchar('"');
				in_printable = 1;
			}
			tty_printable_char(*data_pos);
			data_pos++;
		}
		need_comma = 1;
	}
	if (in_printable != 0)
		putchar('"');
	free(data);
}

