/*
 * (C) Copyright Fuji Xerox Co., Ltd. All rights are 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., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 * 2019/09/04	nobuo.iwata@fujixerox.co.jp
 */

#include "fxlsm_net.h"
#include "fxlsm_ops.h"
#include "fxlsm_event.h"
#include "fxlsm_log.h"
#include <linux/un.h>
#include <linux/in.h>
#include <net/ipv6.h>
#include <linux/in6.h>
#include <linux/netlink.h>
#include <linux/slab.h>
#include <linux/mm.h>
#include <linux/ctype.h>

static unsigned long flags;

#define FXLSM_NET	0

static inline int is_net_enabled(void)
{
	return test_bit(FXLSM_NET, &flags);
}

int fxlsm_get_net(void)
{
	return is_net_enabled();
}

int fxlsm_set_net(int stat)
{
	if (stat)
		return test_and_set_bit(FXLSM_NET, &flags);
	return test_and_clear_bit(FXLSM_NET, &flags);
}

#ifdef CONFIG_SECURITY_NETWORK
#define UCHAR_DIGITS 3
#define USHORT_DIGITS 5
#define U32_DIGITS 10
#define USHORT_HEX 4
#define CHARS_FAMILY_UNIX 4
#define CHARS_FAMILY_INET 4
#define CHARS_FAMILY_INET6 5
#define CHARS_FAMILY_UNIX 4
#define CHARS_FAMILY_NETLINK 7
#define CHARS_HEAD_UNIX (CHARS_FAMILY_UNIX + 1)
#define CHARS_HEAD_INET (CHARS_FAMILY_INET + 2)
#define CHARS_HEAD_INET6 (CHARS_FAMILY_INET6 + 2)
#define CHARS_HEAD_NETLINK (CHARS_FAMILY_NETLINK + 2)
#define CHARS_HEAD_OTHERS (USHORT_DIGITS + 2)
#define NUM_INET6_BRACKETS 2

static const char *unix_unnamed = "(unnamed)";
static const char *unix_abstract = "(abstract)";

static int __check_sun_path(const char *path, int bytes)
{
	int i, ret = 0;

	for (i = 0; i < bytes; i++) {
		if (!isprint(*(path + i)))
			++ret;
	}
	return ret;
}

static void __set_sun_path(char *buf, const char *path, int bytes)
{
	int i, off = 0;
	char c;

	for (i = 0; i < bytes; i++) {
		c = *(path + i);
		if (isprint(c)) {
			*(buf + off) = c;
			++off;
		} else {
			sprintf(buf + off, "%%%02X", (unsigned char)c);
			off += 3;
		}
	}
	*(buf + off) = 0;
}

static char *encode_unix(struct sockaddr_storage *addr, int len)
{
	struct sockaddr_un *__addr = (struct sockaddr_un *)addr;
	const char *p;
	int none_print = 0;
	int bytes, bufsz, __len;
	char *buf;

	if (len == sizeof(__kernel_sa_family_t)) {
		p = unix_unnamed;
		bytes = strlen(p);
	} else {
		p = __addr->sun_path;
		__len = len - offsetof(struct sockaddr_un, sun_path);
		/* may not null terminated so not subtracted one */
		__len = (__len < UNIX_PATH_MAX) ? __len : UNIX_PATH_MAX;
		bytes = strnlen(p, __len);
		if (bytes <= 0) {
			p = unix_abstract;
			bytes = strlen(p);
		} else {
			none_print = __check_sun_path(p, bytes);
		}
	}
	bufsz = CHARS_HEAD_UNIX + bytes + (none_print * 2) + 1;
	buf = kmalloc(bufsz, GFP_KERNEL);
	if (!buf) {
		ERR("kmalloc %d", bufsz);
		return NULL;
	}
	strcpy(buf, "unix:");
	if (none_print) {
		__set_sun_path(buf + CHARS_HEAD_UNIX, p, bytes);
	} else {
		memcpy(buf + CHARS_HEAD_UNIX, p, bytes);
		*(buf + CHARS_HEAD_UNIX + bytes) = 0;
	}
	return buf;
}

static char *encode_inet(struct sockaddr_storage *addr, int len)
{
	struct sockaddr_in *__addr = (struct sockaddr_in *)addr;
	int bufsz;
	char *buf;
	unsigned char *p;

	if (len < sizeof(struct sockaddr_in)) {
		ERR("invalid inet addr len %d", len);
		return NULL;
	}
	bufsz = CHARS_HEAD_INET + ((UCHAR_DIGITS + 1) * 4) + USHORT_DIGITS + 1;
	buf = kmalloc(bufsz, GFP_KERNEL);
	if (!buf) {
		ERR("kmalloc %d", bufsz);
		return NULL;
	}
	p = (unsigned char *)&(__addr->sin_addr.s_addr);
	snprintf(buf, bufsz, "inet:/%pI4:%u",
		&(__addr->sin_addr.s_addr), ntohs(__addr->sin_port));
	return buf;
}

static char *encode_inet6(struct sockaddr_storage *addr, int len)
{
	struct sockaddr_in6 *__addr = (struct sockaddr_in6 *)addr;
	int bufsz;
	char *buf;

	if (len < SIN6_LEN_RFC2133) {
		ERR("invalid inet6 addr len %d", len);
		return NULL;
	}
	bufsz = CHARS_HEAD_INET6 + ((USHORT_HEX + 1) * 8) + USHORT_DIGITS + 
		NUM_INET6_BRACKETS + 1;
	buf = kmalloc(bufsz, GFP_KERNEL);
	if (!buf) {
		ERR("kmalloc %d", bufsz);
		return NULL;
	}
	snprintf(buf, bufsz, "inet6:/[%pI6]:%u",
		&(__addr->sin6_addr), ntohs(__addr->sin6_port));
	return buf;
}

static char *encode_netlink(struct sockaddr_storage *addr, int len)
{
	struct sockaddr_nl *__addr = (struct sockaddr_nl *)addr;
	int bufsz;
	char *buf;

	if (len < sizeof(struct sockaddr_nl)) {
		ERR("invalid netlink addr len %d", len);
		return NULL;
	}
	bufsz = CHARS_HEAD_NETLINK + ((U32_DIGITS + 1) * 2);
	buf = kmalloc(bufsz, GFP_KERNEL);
	if (!buf) {
		ERR("kmalloc %d", bufsz);
		return NULL;
	}
	snprintf(buf, bufsz, "netlink:/%u:%u",
		__addr->nl_pid, __addr->nl_groups);
	return buf;
}

static char *encode_others(struct sockaddr_storage *addr, int len)
{
	int bytes, bufsz, i;
	char *buf, *p;

	bytes = len - sizeof(unsigned short);
	bufsz = CHARS_HEAD_OTHERS + (bytes * 3);
	buf = kmalloc(bufsz, GFP_KERNEL);
	if (!buf) {
		ERR("kmalloc %d", bufsz);
		return NULL;
	}
	sprintf(buf, "%u:/", addr->ss_family);
	p = buf + strlen(buf);
	for (i = 0; i < bytes; i++) {
		if (i == (bytes - 1))
			sprintf(p + (i * 3), "%02x", *(addr->__data + i));
		else
			sprintf(p + (i * 3), "%02x:", *(addr->__data + i));
	}
	return buf;
}

static char *encode(struct sockaddr_storage *addr, int len)
{
	switch (addr->ss_family) {
	case AF_UNIX:
		return encode_unix(addr, len);
	case AF_INET:
		return encode_inet(addr, len);
	case AF_INET6:
		return encode_inet6(addr, len);
	case AF_NETLINK:
		return encode_netlink(addr, len);
	}
	return encode_others(addr, len);
}

static void notify(const char *e, struct sockaddr_storage *addr, int len)
{
	char *buf;
	const char *comm;

	buf = encode(addr, len);
	if (!buf)
		return;
	comm = __comm();
	fxlsm_notify_net(e, comm, buf);
	kfree(buf);
}
 
static int __addr2storage(struct sockaddr_storage *__addr, int *__len,
		struct sockaddr *addr, int len)
{
	if (!addr || len <= 0)
		return 0;
	*__len = (len < _K_SS_MAXSIZE) ? len : _K_SS_MAXSIZE;
	memcpy(__addr, addr, *__len);
	return 1;
}

static inline int addr2storage(struct sockaddr_storage *__addr, int *__len,
		struct sockaddr *addr, int len)
{
	int ret;

	rcu_read_lock();
	ret = __addr2storage(__addr, __len, addr, len);
	rcu_read_unlock();
	return ret;
}

static int __msghdr2storage(struct sockaddr_storage *__addr, int *__len,
		struct msghdr *msg)
{
	if (!msg || !msg->msg_name || msg->msg_namelen <= 0)
		return 0;
	*__len = (msg->msg_namelen < _K_SS_MAXSIZE) ?
		msg->msg_namelen : _K_SS_MAXSIZE;
	memcpy(__addr, msg->msg_name, *__len);
	return 1;
}

static inline int msghdr2storage(struct sockaddr_storage *__addr, int *__len,
		struct msghdr *msg)
{
	int ret;

	rcu_read_lock();
	ret = __msghdr2storage(__addr, __len, msg);
	rcu_read_unlock();
	return ret;
}

#if LINUX_VERSION_CODE >= KERNEL_VERSION(4,17,0)
static int inline sockname(struct socket *sock, struct sockaddr *addr,
		int *len,  int peer)
{
	int ret;

	if (!sock || !sock->ops || !sock->ops->getname)
		return -ENOENT;
	ret = sock->ops->getname(sock, addr, peer);
	if (ret < 0)
		return ret;
	*len = ret;
	return 0;
}
#else
static int inline sockname(struct socket *sock, struct sockaddr *addr,
		int *len, int peer)
{
	if (!sock || !sock->ops || !sock->ops->getname)
		return -ENOENT;
	return sock->ops->getname(sock, addr, len, peer);
}
#endif

static int __socket2storage(struct sockaddr_storage *__addr, int *__len,
		struct socket *sock, int peer)
{
	int ret;

	ret = sockname(sock, (struct sockaddr *)__addr, __len, peer);
	if (ret) {
		ERR("sockname %d %d", peer, ret);
		return 0;
	}
	return 1;
}

static inline int socket2storage(struct sockaddr_storage *__addr, int *__len,
		struct socket *sock, int peer)
{
	int ret;

	rcu_read_lock();
	ret = __socket2storage(__addr, __len, sock, peer);
	rcu_read_unlock();
	return ret;
}

int fxlsm_socket_bind(struct socket *sock, struct sockaddr *addr, int len)
{
	struct sockaddr_storage __addr;
	int __len;

	if (is_net_enabled() &&
		addr2storage(&__addr, &__len, addr, len))
		notify(fxlsm_e_bind, &__addr, __len);
	return 0;
}

int fxlsm_socket_connect(struct socket *sock, struct sockaddr *addr, int len)
{
	struct sockaddr_storage __addr;
	int __len;

	if (is_net_enabled() &&
		addr2storage(&__addr, &__len, addr, len))
		notify(fxlsm_e_connect, &__addr, __len);
	return 0;
}

int fxlsm_socket_listen(struct socket *sock, int backlog)
{
	struct sockaddr_storage __addr;
	int __len;

	if (is_net_enabled() &&
		__socket2storage(&__addr, &__len, sock, 0))
		notify(fxlsm_e_listen, &__addr, __len);
	return 0;
}

int fxlsm_socket_accept(struct socket *sock, struct socket *newsock)
{
	struct sockaddr_storage __addr;
	int __len;

	/*
	 *  This hook will be called at the beginning of accept.
	 *  The 'sock' doesn't have peer and the 'newsock' have not yet
	 *  initialized. So, this hook shows source address.
	 *  Peer addreass will be handled by sock_graft().
	 */
	if (is_net_enabled() &&
		__socket2storage(&__addr, &__len, sock, 0))
		notify(fxlsm_e_accept, &__addr, __len);
	return 0;
}

int fxlsm_socket_sendmsg(struct socket *sock, struct msghdr *msg, int size)
{
	struct sockaddr_storage __addr;
	int __len;

	if (is_net_enabled() &&
		msghdr2storage(&__addr, &__len, msg))
		notify(fxlsm_e_sendmsg, &__addr, __len);
	return 0;
}

int fxlsm_socket_recvmsg(struct socket *sock, struct msghdr *msg, int size,
		int flags)
{
	struct sockaddr_storage __addr;
	int __len;

	if (is_net_enabled() &&
		msghdr2storage(&__addr, &__len, msg))
		notify(fxlsm_e_recvmsg, &__addr, __len);
	return 0;
}

void fxlsm_sock_graft(struct sock *sk, struct socket *parent)
{
	struct sockaddr_storage __addr;
	int __len;
	int family;

	if (!is_net_enabled())
		return;
	/*
	 * It is known that parent socket is not valid for AF_UNIX.
	 * parent->sk is not valid so unix_getname() cases oops.
	 */
	family = parent->ops->family;
	if (family == AF_UNIX)
		return;
	if (__socket2storage(&__addr, &__len, parent, 2))
		notify(fxlsm_e_graft, &__addr, __len);
	return;
}
#endif /* CONFIG_SECURITY_NETWORK */
