/*
 * Copyright (c) 2001, 2002, 2003, 2004, 2005  Netli, Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. 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.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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.
 *
 * $Id: ncnf_vr.c,v 1.1 2005/05/26 12:08:19 vlm Exp $
 */
#include "headers.h"
#include "ncnf_int.h"
#include "ncnf_vr.h"

/*
 * Forward declarations
 */
static int _ncnf_vr_validate(struct vr_config *vc, struct ncnf_obj_s *obj);
static int _vr_check_entity(struct vr_config *vc, struct ncnf_obj_s *obj, struct vr_entity *e, int check_results);
static int _vr_check_rule(struct vr_config *vc, struct ncnf_obj_s *obj, struct vr_rule *rule);

static char *
__vr_obj_class2string(enum vr_obj_class vr_obj_class) {
	switch(vr_obj_class) {
	case VR_CLASS_ATTRIBUTE:
		return "attribute";
	case VR_CLASS_ENTITY:
		return "entity";
	case VR_CLASS_REFERENCE:
		return "reference";
	case VR_CLASS_ATTACHMENT:
		return "attachment";
	}

	return "UNKNOWN";
}

static collection_t *
__vr_collection_by_obj_class(struct ncnf_obj_s *obj, enum vr_obj_class vr_obj_class) {
	int idx;

	if(obj->obj_class != NOBJ_ROOT && obj->obj_class != NOBJ_COMPLEX)
		return NULL;

	switch(vr_obj_class) {
	case VR_CLASS_ATTRIBUTE:
		idx = COLLECTION_ATTRIBUTES;
		break;
	case VR_CLASS_ENTITY:
	case VR_CLASS_REFERENCE:
	case VR_CLASS_ATTACHMENT:
		idx = COLLECTION_OBJECTS;
		break;
	default:
		assert(0);
		return NULL;
	}

	return &obj->m_collection[idx];
}

void
ncnf_vr_destroy(struct vr_config *vc) {
	if(vc) {
		if(vc->entities)
			genhash_destroy(vc->entities);
		if(vc->types)
			genhash_destroy(vc->types);
		free(vc);
	}
}


int
ncnf_validate(struct ncnf_obj_s *obj, struct vr_config *vc) {

	if(obj == NULL || vc == NULL) {
		errno = EINVAL;
		return -1;
	}

	if(_ncnf_vr_validate(vc, obj)) {
		return -1;
	}

	return 0;
}


static int
_ncnf_vr_validate(struct vr_config *vc, struct ncnf_obj_s *obj) {
	struct vr_entity *e;
	int i;

	assert(vc && obj);

	/*
	 * Checking root object.
	 */
	if(obj->obj_class == NOBJ_ROOT) {

		e = _vr_get_entity(vc, "ROOT", NULL, 0);
		if(e == NULL) {
			/* Unknown entity */
			return 0;
		}
	
		if(_vr_check_entity(vc, obj, e, 1))
			return -1;
	} else if(obj->obj_class == NOBJ_COMPLEX) {

		e = _vr_get_entity(vc, obj->type, obj->value, 0);
		if(e == NULL) {
			/* Unknown entity */
			return 0;
		}
	} else {
		return 0;
	}

	/*
	 * Checking descendent objects.
	 */

	for(i = 0; i < obj->m_collection[COLLECTION_OBJECTS].entries; i++) {
		struct ncnf_obj_s *next_obj
			= obj->m_collection[COLLECTION_OBJECTS].entry[i].object;

		e = _vr_get_entity(vc, next_obj->type, next_obj->value, 0);
		if(e == NULL) {
			/* Dont touch unknown entity */
			continue;
		}

		if(_vr_check_entity(vc, next_obj, e, 1))
			return -1;

		/*
		 * Recursively go down the tree.
		 */
		if(_ncnf_vr_validate(vc, next_obj))
			return -1;

	}



	return 0;
}


static int
_vr_check_entity(struct vr_config *vc, struct ncnf_obj_s *obj, struct vr_entity *e, int check_results) {
	collection_t *coll;
	struct vr_rule *rule;
	int i;

	assert(vc && obj && e);

	if(e->already_here)
		return 0;
	else
		e->already_here = 1;

	for(rule = e->rules; rule; rule = rule->next) {
		if(_vr_check_rule(vc, obj, rule))
			break;
	}

	e->already_here = 0;

	if(rule)
		return -1;

	if(check_results == 0)
		return 0;

	if(e->rules == NULL)
		/* Allow everything by default */
		return 0;

	if(obj->obj_class != NOBJ_ROOT && obj->obj_class != NOBJ_COMPLEX)
		return 0;

	/*
	 * Checking if everything is marked as checked.
	 */

	coll = &obj->m_collection[COLLECTION_OBJECTS];
	for(i = 0; i < coll->entries; i++) {
		struct ncnf_obj_s *o;
		o = coll->entry[i].object;
		if(o->mark == 0) {
			_ncnf_debug_print(1, "Object `%s \"%s\"' at line %d used in `%s \"%s\"` at line %d is not mentioned in ruleset for entity `%s%s%s%s'",
				o->type, o->value,
				o->config_line,
				obj->type,
				obj->value,
				obj->config_line,
				e->type,
				e->name?" \"":"",
				e->name?e->name:"",
				e->name?"\"":""
			);
			return -1;
		}
	}

	coll = &obj->m_collection[COLLECTION_ATTRIBUTES];
	for(i = 0; i < coll->entries; i++) {
		struct ncnf_obj_s *o;
		o = coll->entry[i].object;
		if(o->mark == 0) {
			_ncnf_debug_print(1, "Attribute `%s \"%s\"' at line %d is not mentioned in ruleset for entity `%s%s%s%s'",
				o->type,
				o->value,
				o->config_line,
				e->type,
				e->name?" \"":"",
				e->name?e->name:"",
				e->name?"\"":""
			);
			return -1;
		}
	}

	return 0;
}

static int
_vr_check_rule(struct vr_config *vc, struct ncnf_obj_s *obj, struct vr_rule *rule) {
	collection_t *coll;
	int count = 0;
	int rule_name_star = 0;
	int i;

	assert(vc && obj && rule);

	coll = __vr_collection_by_obj_class(obj, rule->vr_obj_class);
	if(coll == NULL)
		return 0;

	rule_name_star = strcmp(rule->name, "*")?0:1;

	for(i = 0; i < coll->entries; i++) {
		struct ncnf_obj_s *found = coll->entry[i].object;
		struct vr_type *ty;
		char *value;

		if(!rule_name_star && strcmp(found->type, rule->name))
			continue;

		count++;

		if(rule->vr_obj_class == VR_CLASS_REFERENCE
		|| rule->vr_obj_class == VR_CLASS_ATTACHMENT) {
			if(found->obj_class != NOBJ_REFERENCE) {
				_ncnf_debug_print(1,
					"Reference requested, "
					"but object is not a reference "
					"at line %d",
					found->config_line
				);
				return -1;
			}

			if(rule->vr_obj_class == VR_CLASS_ATTACHMENT) {
				if((found->m_ref_flags & 1) == 0) {
					_ncnf_debug_print(1,
						"Attachment requested "
						"but plain reference found "
						"at line %d",
						found->config_line
					);
					return -1;
				}
			}
		}

		if(found->mark == 1) {
			/* Already checked */
			continue;
		}

		if((rule->multiple == 0) && (count > 1)) {
			_ncnf_debug_print(1, "Single %s %s required, multiple found at line %d",
				__vr_obj_class2string(rule->vr_obj_class),
				rule->name,
				found->config_line
			);
			return -1;
		}

		if(found->mark == 0)
			found->mark = 1;

		if(rule->type == NULL)
			goto skip_type_checks;
		ty = rule->type;

		value = found->value;
		if(value == NULL) value = "";

		if(ty->range_defined) {
			double val = atof(value);
			if(val < ty->range_start
			  || val > ty->range_end) {
				_ncnf_debug_print(1, "Value \"%s\" at line %d does not fit in defined range (%.3f - %.3f)",
					value,
					found->config_line,
					ty->range_start,
					ty->range_end);
				return -1;
			}
		}

		if(ty->regex) {
#ifdef	HAVE_LIBSTRFUNC
			char *results;
			results = sed_exec(ty->regex_compiled, value);
			if(results == NULL) {
				_ncnf_debug_print(1, "Value \"%s\" at line %d does not match regular expression \"%s\"",
					value,
					found->config_line,
					ty->regex);
				return -1;
			}
			if(ty->regex[0] == 's' || ty->regex[0] == 'y') {
				found->mark = 2;
				if(strcmp(value, results)) {
					bstr_t b;
					b = str2bstr(results, -1);
					if(b == NULL) {
						_ncnf_debug_print(1,
						"Memory allocation failed");
						return -1;
					}
					bstr_free(value);
					found->value = b;
				}
			}
#else	/* !HAVE_LIBSTRFUNC */
			assert(!ty->regex);
#endif	/* HAVE_LIBSTRFUNC */
		}

		if(ty->ip_required) {
			struct in_addr ip;
			if(inet_aton(value, &ip) != 1) {
				_ncnf_debug_print(1, "Value \"%s\" at line %d is not an IP address",
					value,
					found->config_line);
				return -1;
			}
		}

		if(ty->ip_mask_required) {
#ifdef	HAVE_LIBSTRFUNC
			unsigned int ip, mask;
			if(strchr(value, ' ')
				|| split_network(value, &ip, &mask)
			) {
				_ncnf_debug_print(1, "Value \"%s\" at line %d is not an IP address/Mask",
					value,
					found->config_line);
				return -1;
			}
#else
			assert(!ty->ip_mask_required);
#endif	/* HAVE_LIBSTRFUNC */
		}

		if(ty->ip_masklen_required) {
#ifdef	HAVE_LIBSTRFUNC
			char *p;
			unsigned int ip, mask;
			if((p = strchr(value, ' '))
				|| !(p = strchr(value, '/'))
				|| strlen(p) > 3
				|| split_network(value, &ip, &mask)
			) {
				_ncnf_debug_print(1, "Value \"%s\" at line %d is not an IP address/Masklen",
					value,
					found->config_line);
				return -1;
			}
#else
			assert(!ty->ip_masklen_required);
#endif	/* HAVE_LIBSTRFUNC */
		}

		if(ty->ip_mask_required) {
			char *p;
			struct in_addr ip;

			if( (p = strchr(value, ':')) == NULL
				|| atoi(p+1) == 0
				|| (*p = '\0' && 0) /* Mask ':' */
				|| inet_aton(value, &ip) != 1
			) {
				if(p) *p = ':';	/* Unmask ':' */
				_ncnf_debug_print(1, "Value \"%s\" at line %d is not an ip:port",
					value,
					found->config_line);
				return -1;
			}
			if(p) *p = ':';	/* Unmask ':' */
		}

	skip_type_checks:

		if(rule->_entity_reference) {
			char *e_type, *e_name;
			struct vr_entity *e;

			e_type = alloca(strlen(found->value) + 1);
			strcpy(e_type, found->value);
			e_name = strchr(e_type, ':');
			if(e_name)
				*e_name++ = '\0';


			e = _vr_get_entity(vc, e_type, e_name, 0);
			if(e == NULL) {
				_ncnf_debug_print(1,
				"Reference to the unknown validation entity %s at line %d",
					e_type,
					found->config_line
				);
				return -1;
			}

			if(_vr_check_entity(vc, obj, e, 0)) {
				return -1;
			}

		}


	}

	if((rule->mandatory != 0) && (count == 0)) {
		_ncnf_debug_print(1,
			"Mandatory %s %s missing in entity `%s \"%s\"' at line %d",
			__vr_obj_class2string(rule->vr_obj_class),
			rule->name,
			obj->type?obj->type:"ROOT",
			obj->value?obj->value:"<unnamed>",
			obj->config_line
		);
		return -1;
	}



	return 0;
}




syntax highlighted by Code2HTML, v. 0.9.1