/*********************************************************************************************************
* Software License Agreement (BSD License)                                                               *
* Author: Sebastien Decugis <sdecugis@freediameter.net>							 *
*													 *
* Copyright (c) 2023, WIDE Project and NICT								 *
* All rights reserved.											 *
* 													 *
* Redistribution and use of this software in source and binary forms, with or without modification, are  *
* permitted provided that the following conditions are met:						 *
* 													 *
* * Redistributions of source code must retain the above 						 *
*   copyright notice, this list of conditions and the 							 *
*   following disclaimer.										 *
*    													 *
* * Redistributions in binary form must reproduce the above 						 *
*   copyright notice, this list of conditions and the 							 *
*   following disclaimer in the documentation and/or other						 *
*   materials provided with the distribution.								 *
* 													 *
* * Neither the name of the WIDE Project or NICT nor the 						 *
*   names of its contributors may be used to endorse or 						 *
*   promote products derived from this software without 						 *
*   specific prior written permission of WIDE Project and 						 *
*   NICT.												 *
* 													 *
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED *
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A *
* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR *
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 	 *
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 	 *
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR *
* TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF   *
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.								 *
*********************************************************************************************************/

#include "acl_wl.h"

/* The configuration simply contains the allowed fqdn and/or domains (*.example.net)
 * It is represented similarly to the DNS tree:
 *              (root)--___
 *              /    \     \
 *            tld1  tld2   (tld3...)
 *             /      |
 *          label1   label2
 *                    /   \
 *                 lbl21 lbl22
 *                  /       \
 *               lbl211      *
 *
 * This tree would whitelist:
 *   - label1.tld1
 *   - lbl211.lbl21.label2.tld2
 *   - *.lbl22.label2.tld2
 *
 * The functions to add and search the tree are in aw_tree.c.
 *
 */
 
/* Maximum depth of the tree. We set a static size to avoid dynamic allocations. We report an error if this is not sufficient. */
#define AW_TREE_MAXDEPTH 10

/* An element of the tree */
struct tree_item {
	struct fd_list	chain;		/* Link to elements at the same level. Ordered alphabetically. */
	struct fd_list	children;	/* Sentinel for the subtree. */
	char *		str;	/* the \0 terminated label, or NULL if it is a generic container ("*") */
	int		flags;	/* PI_SEC_* flags */
	int		leaf;	/* true if this item can be a leaf of the tree */
};

/* The root of the tree */
struct fd_list tree_root = FD_LIST_INITIALIZER(tree_root);

/* Note: we lock accesses to the tree with acl_wl_lock because of config reload */


/* The parsed name */
struct split_name {
	struct {
		char * str;	/* start of this label */
		size_t len;	/* length of this label. It does not include the final "." or "\0" */
	}   label[AW_TREE_MAXDEPTH];
	int last_lbl;	/* idx of last label defined */
};

/* The following function explodes a name into a split_name structure */
static int parse_name(char * name, struct split_name * result)
{
	int i, l, prev_offset;
	
	TRACE_ENTRY("%p %p", name, result);
	
	/* First, initialize the result array */
	memset(result, 0, sizeof(struct split_name));
	result->label[0].str = name;
	l = 0; prev_offset = 0;
	
	for (i=0; name[i] != '\0'; i++) {
		if (name[i]=='.') {
			l++;
			CHECK_PARAMS( l < AW_TREE_MAXDEPTH );
			
			/* The previous label is complete, write its size */
			result->label[l - 1].len = i - prev_offset;
			prev_offset = i + 1;
			
			/* Write the start of the new label */
			result->label[l].str = name + i + 1;
		}
	}
	
	/* Finally, write the size of the last label */
	result->label[l].len = i - prev_offset;
	
	result->last_lbl = l;
	
#if 0
	fd_log_debug("Parsed name %s as:", name);
	for (i=0; i<=l; i++)
		fd_log_debug("  str[%d] len: %d, v:%.*s", i, result->label[i].len, result->label[i].len, result->label[i].str);
#endif /* 0 */
	return 0;
}

/* Create a new tree_item structure */
static struct tree_item * new_ti(char * str, size_t len, int flags, int leaf)
{
	struct tree_item * ti;
	char * s = NULL;
	
	TRACE_ENTRY("%p %zd %x", str, len, flags);
	
	if (str) {
		CHECK_MALLOC_DO(s = malloc(len + 1), return NULL);
		memcpy(s, str, len);
		s[len] = '\0';
	}
	
	CHECK_MALLOC_DO( ti = malloc(sizeof(struct tree_item)), {free(s); return NULL; } );
	memset(ti, 0, sizeof(struct tree_item));
	
	fd_list_init(&ti->chain, ti);
	fd_list_init(&ti->children, ti);
	ti->str = s;
	ti->flags = flags;
	ti->leaf = leaf;
	
	return ti;
}

/* Recursively delete a subtree */
static void delete_tree(struct fd_list * senti)
{
	while (!FD_IS_LIST_EMPTY(senti)) {
		struct tree_item * ti = (struct tree_item *)(senti->next);
		
		/* Delete recursively its children first */
		delete_tree(&ti->children);
		
		/* Now, unlink from the sentinel list */
		fd_list_unlink(&ti->chain);
		
		/* destroy this tree item */
		free(ti->str);
		free(ti);
	}
}

/* Top-level destroy function */
void aw_tree_destroy(void)
{
	delete_tree(&tree_root);
}

/* Display the content of a subtree */
static void tree_dump(struct fd_list * sub, int indent)
{
	struct fd_list * li;
	for (li = sub->next; li != sub; li = li->next) {
		struct tree_item * ti = (struct tree_item *)li;
		char buf[1024];
		snprintf(buf, sizeof(buf), "%*s%s", indent * 2, "", ti->str?:"*");
		if (ti->leaf)
			snprintf(buf+strlen(buf), sizeof(buf)-strlen(buf), " (flag:%x)", ti->flags);
		fd_log_debug("%s", buf);
		tree_dump(&ti->children, indent + 1);
	}
}

/* Top-level function */
void aw_tree_dump(void)
{
	fd_log_debug("[acl_wl] tree dump:");
	fd_log_debug("(root)");
	tree_dump(&tree_root, 1);
	fd_log_debug("[acl_wl] end of dump");
}

/* Function to add a new entry in the tree */
int aw_tree_add(char * name, int flags)
{
	struct split_name sn;
	struct tree_item * ti;
	struct fd_list * li, *senti;
	int lbl, found;
	
	TRACE_ENTRY("%p %x", name, flags);
	CHECK_PARAMS(name && *name);
	
	CHECK_FCT_DO( parse_name(name, &sn), 
		{ 
			fd_log_debug("The name '%s' contains too many labels, try a generic (*) or recompile with bigger AW_TREE_MAXDEPTH value (cur: %d)", name, AW_TREE_MAXDEPTH); 
			return EINVAL; 
		} );
		
	senti = &tree_root;
	
	for (lbl = sn.last_lbl; lbl > 0; lbl--) {
		/* Check if the list is empty, we can directly create the new entry */
		if (FD_IS_LIST_EMPTY(senti)) {
			CHECK_MALLOC( ti = new_ti(sn.label[lbl].str, sn.label[lbl].len, 0, 0 /* flags are only set in the terminals */) );
			/* Insert this label in the sentinel sublist */
			fd_list_insert_after(senti, &ti->chain);
			/* Update the sentinel */
			senti = &ti->children;
			/* loop to the next label */
			continue;
		}
		
		/* Check if we have a '*' element already that overlapses */
		ti = (struct tree_item *)(senti->next);
		if (ti->str == NULL) {
			fd_log_debug("[acl_wl] Warning: entry '%s' is superseded by a generic entry at label %d, ignoring.", name, lbl + 1);
			return 0;
		}
		
		/* Search this label in the ordered list */
		found = 0;
		for (li = senti->next; li != senti; li=li->next) {
			int cmp, len;
			ti = (struct tree_item *)li;
			
			cmp = strncasecmp(ti->str, sn.label[lbl].str, sn.label[lbl].len);
			if (cmp > 0)
				break;	/* the new label must be inserted before li */
			if (cmp < 0)
				continue;
			
			/* Check the lengths */
			len = strlen(ti->str);
			if (len > sn.label[lbl].len)
				break;	/* the new label must be inserted before li */
			if (len < sn.label[lbl].len)
				continue;
			
			/* We already had this label */
			found = 1;
			senti = &ti->children;
			break;
		}
		
		if (found)
			continue;
		
		/* Otherwise, we have to create a new ti, and add it before li */
		CHECK_MALLOC( ti = new_ti(sn.label[lbl].str, sn.label[lbl].len, 0, 0 /* flags are only set in the terminals */) );
		/* Insert this label in the sentinel sublist */
		fd_list_insert_before(li, &ti->chain);
		/* Update the sentinel */
		senti = &ti->children;
	}
	
	ti = NULL;
	li = senti;
	
	/* At this point, senti points to the list where we are supposed to insert our last label. */
	if (sn.label[0].str[0] == '*') {
		if (!FD_IS_LIST_EMPTY(senti)) {
			fd_log_debug("[acl_wl] Warning: entry '%s' overwrites previous more detailed entries, these are deleted.", name);
			delete_tree(senti);
		}
		
		/* Create the new entry */
		CHECK_MALLOC( ti = new_ti(NULL, 0, flags, 1) );
	} else {
		if (!FD_IS_LIST_EMPTY(senti)) {
			/* Check we don't have a '*' entry already */
			ti = (struct tree_item *)(senti->next);
			if (ti->str == NULL) {
				fd_log_debug("[acl_wl] Warning: entry '%s' is superseded by a generic entry at label 1, ignoring.", name);
				return 0;
			}
			
			/* Search the place for the new label */
			for (li = senti->next; li != senti; li=li->next) {
				int cmp, len;
				ti = (struct tree_item *)li;

				cmp = strncasecmp(ti->str, sn.label[0].str, sn.label[0].len);
				if (cmp > 0)
					break;	/* the new label must be inserted before li */
				if (cmp < 0)
					continue;

				/* Check the lengths */
				len = strlen(ti->str);
				if (len > sn.label[0].len)
					break;	/* the new label must be inserted before li */
				if (len < sn.label[0].len)
					continue;

				/* We already had this label */
				if (ti->leaf) {
					fd_log_debug("[acl_wl] Warning: entry '%s' is duplicated, merging the flags.", name);
					ti->flags |= flags;
					return 0;
				} else {
					/* Just mark this entry as a valid leaf also */
					ti->leaf = 1;
					ti->flags = flags;
					return 0;
				}
			}
		}
		
		/* Create the new entry */
		CHECK_MALLOC( ti = new_ti(sn.label[0].str, sn.label[0].len, flags, 1) );
	}
	
	/* The new label is "ti", it is inserted before "li" */
	fd_list_insert_before(li, &ti->chain);
	
	/* Done! */
	return 0;
}

/* Search in the tree. On return, *result =  -1: not found; >=0: found with PI_SEC_* flags */
int aw_tree_lookup(char * name, int * result)
{
	struct split_name sn;
	int lbl, found;
	struct tree_item * ti = NULL;
	struct fd_list * senti, *li;
	
	TRACE_ENTRY("%p %p", name, result);
	CHECK_PARAMS(name && result);
	
	/* Initialize */
	*result = -1;
	
	/* Parse the name into labels */
	CHECK_FCT_DO( parse_name(name, &sn), 
		{ 
			TRACE_DEBUG(INFO, "Too many labels in this name, it cannot be found in the tree, skipping."); 
			return 0;
		} );
	
	senti = &tree_root;
	
	for (lbl = sn.last_lbl; lbl >= 0; lbl--) {
		/* Check if the list is empty, we can directly return */
		if (FD_IS_LIST_EMPTY(senti)) {
			/* The item is not found */
			return 0;
		}
		
		/* Check if we have a '*' element */
		ti = (struct tree_item *)(senti->next);
		if (ti->str == NULL) {
			TRACE_DEBUG(ANNOYING, "[acl_wl] %s matched at label %d with a generic entry.", name, lbl + 1);
			*result = ti->flags;
			return 0;
		}

		/* Search this label in the ordered list */
		found = 0;
		for (li = senti->next; li != senti; li=li->next) {
			int cmp, len;
			ti = (struct tree_item *)li;
			
			cmp = strncasecmp(ti->str, sn.label[lbl].str, sn.label[lbl].len);
			if (cmp > 0)
				return 0;	/* the label was not found */
			if (cmp < 0)
				continue;
			
			/* Check the lengths */
			len = strlen(ti->str);
			if (len > sn.label[lbl].len)
				return 0;	/* the label was not found */
			if (len < sn.label[lbl].len)
				continue;
			
			/* We found the label */
			found = 1;
			senti = &ti->children;
			break;
		}
		
		if (!found)
			return 0; /* label not found */
		
		/* otherwise, continue, sentinel has been updated */
	}
	
	/* At the end, ti points to the correct leaf */
	if (!ti->leaf)
		return 0;
	
	TRACE_DEBUG(ANNOYING, "[acl_wl] %s matched exactly.", name);
	*result = ti->flags;
	return 0;
}
