/* 
   ldb database library

   Copyright (C) Simo Sorce  2004-2006

   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 3 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, see <http://www.gnu.org/licenses/>.
*/

/*
 *  Name: ldb
 *
 *  Component: ldb schema module
 *
 *  Description: add schema syntax functionality
 *
 *  Author: Simo Sorce
 */

#include "includes.h"
#include "ldb/include/ldb.h"
#include "ldb/include/ldb_errors.h"
#include "schema_syntax.h"

int map_schema_syntax(uint32_t om_syntax, const char *attr_syntax, const struct ldb_val *om_class, enum schema_internal_syntax *syntax)
{
	int ret;

	ret = LDB_SUCCESS;

	switch(om_syntax) {
	case 1:
		*syntax = SCHEMA_AS_BOOLEAN;
		break;
	case 2:
		*syntax = SCHEMA_AS_INTEGER;
		break;
	case 4:
		if (strcmp(attr_syntax, "2.5.5.10") == 0) {
			*syntax = SCHEMA_AS_OCTET_STRING;
			break;
		}
		if (strcmp(attr_syntax, "2.5.5.17") == 0) {
			*syntax = SCHEMA_AS_SID;
			break;
		}
		ret = LDB_ERR_OPERATIONS_ERROR;
		break;
	case 6:
		*syntax = SCHEMA_AS_OID;
		break;
	case 10:
		*syntax = SCHEMA_AS_ENUMERATION;
		break;
	case 18:
		*syntax = SCHEMA_AS_NUMERIC_STRING;
		break;
	case 19:
		*syntax = SCHEMA_AS_PRINTABLE_STRING;
		break;
	case 20:
		*syntax = SCHEMA_AS_CASE_IGNORE_STRING;
		break;
	case 22:
		*syntax = SCHEMA_AS_IA5_STRING;
		break;
	case 23:
		*syntax = SCHEMA_AS_UTC_TIME;
		break;
	case 24:
		*syntax = SCHEMA_AS_GENERALIZED_TIME;
		break;
	case 27:
		*syntax = SCHEMA_AS_CASE_SENSITIVE_STRING;
		break;
	case 64:
		*syntax = SCHEMA_AS_DIRECTORY_STRING;
		break;
	case 65:
		*syntax = SCHEMA_AS_LARGE_INTEGER;
		break;
	case 66:
		*syntax = SCHEMA_AS_OBJECT_SECURITY_DESCRIPTOR;
		break;
	case 127:
		if (!om_class) {
			ret = LDB_ERR_OPERATIONS_ERROR;
			break;
		}
		
		if (memcmp(om_class->data, "\x2b\x0c\x02\x87\x73\x1c\x00\x85\x4a\x00", MIN(om_class->length, 10)) == 0) {
			*syntax = SCHEMA_AS_DN;
			break;
		}
		if (memcmp(om_class->data, "\x2a\x86\x48\x86\xf7\x14\x01\x01\x01\x0b", MIN(om_class->length, 10)) == 0) {
			*syntax = SCHEMA_AS_DN_BINARY;
			break;
		}
		if (memcmp(om_class->data, "\x56\x06\x01\x02\x05\x0b\x1d\x00\x00\x00", MIN(om_class->length, 10)) == 0) {
			*syntax = SCHEMA_AS_OR_NAME;
			break;
		}
		if (memcmp(om_class->data, "\x2a\x86\x48\x86\xf7\x14\x01\x01\x01\x06", MIN(om_class->length, 10)) == 0) {
			*syntax = SCHEMA_AS_REPLICA_LINK;
			break;
		}
		if (memcmp(om_class->data, "\x2b\x0c\x02\x87\x73\x1c\x00\x85\x5c\x00", MIN(om_class->length, 10)) == 0) {
			*syntax = SCHEMA_AS_PRESENTATION_ADDRESS;
			break;
		}
		if (memcmp(om_class->data, "\x2b\x0c\x02\x87\x73\x1c\x00\x85\x3e\x00", MIN(om_class->length, 10)) == 0) {
			*syntax = SCHEMA_AS_ACCESS_POINT;
			break;
		}
		if (memcmp(om_class->data, "\x2a\x86\x48\x86\xf7\x14\x01\x01\x01\x0c", MIN(om_class->length, 10)) == 0) {
			*syntax = SCHEMA_AS_DN_STRING;
			break;
		}
		/* not found will error in default: */
	default:
		ret = LDB_ERR_OPERATIONS_ERROR;
	}

	return ret;
}

static int schema_validate_boolean(struct ldb_context *ldb, struct ldb_val *val, int min, int max)
{

	if ((strncmp("TRUE", (const char *)val->data, val->length) != 0) &&
	    (strncmp("FALSE", (const char *)val->data, val->length) != 0)) {
		return LDB_ERR_INVALID_ATTRIBUTE_SYNTAX;
	}

	return LDB_SUCCESS;
}

static int schema_validate_integer(struct ldb_context *ldb, struct ldb_val *val, int min, int max)
{
	int value;
	char *endptr;

	errno = 0;
	value = strtol((const char *)val->data, &endptr, 0);
	if (errno) return LDB_ERR_INVALID_ATTRIBUTE_SYNTAX;
	if (endptr[0] != '\0') return LDB_ERR_INVALID_ATTRIBUTE_SYNTAX;
	if ((min > INT_MIN) && (value < min)) return LDB_ERR_INVALID_ATTRIBUTE_SYNTAX;
	if ((max < INT_MAX) && (value > max)) return LDB_ERR_INVALID_ATTRIBUTE_SYNTAX;

	return LDB_SUCCESS;
}

static int schema_validate_binary_blob(struct ldb_context *ldb, struct ldb_val *val, int min, int max)
{
	/* is there anythign we should check in a binary blob ? */
	return LDB_SUCCESS;
}

static int schema_validate_sid(struct ldb_context *ldb, struct ldb_val *val, int min, int max)
{
	/* TODO: validate binary form of objectSid */
	return LDB_SUCCESS;	
}

static int schema_validate_oid(struct ldb_context *ldb, struct ldb_val *val, int min, int max)
{
	if (strspn((const char *)val->data, "0123456789.") != val->length)
		return LDB_ERR_INVALID_ATTRIBUTE_SYNTAX;

	return LDB_SUCCESS;
}

static int schema_validate_numeric_string(struct ldb_context *ldb, struct ldb_val *val, int min, int max)
{
	if (strspn((const char *)val->data, "0123456789") != val->length)
		return LDB_ERR_INVALID_ATTRIBUTE_SYNTAX;

	return LDB_SUCCESS;
}

static int schema_validate_printable_string(struct ldb_context *ldb, struct ldb_val *val, int min, int max)
{
	/* TODO: find out what constitutes the printable character set */
	return LDB_SUCCESS;
}

static int schema_validate_teletext_string(struct ldb_context *ldb, struct ldb_val *val, int min, int max)
{
	/* TODO: find out what constitutes the teletext character set */
	return LDB_SUCCESS;
}

static int schema_validate_ia5_string(struct ldb_context *ldb, struct ldb_val *val, int min, int max)
{
	/* TODO: find out what constitutes the IA5 character set */
	return LDB_SUCCESS;
}

static int schema_validate_utc_time(struct ldb_context *ldb, struct ldb_val *val, int min, int max)
{
	/* TODO: validate syntax of UTC Time string */
	return LDB_SUCCESS;
}

static int schema_validate_generalized_time(struct ldb_context *ldb, struct ldb_val *val, int min, int max)
{
	/* TODO: validate syntax of Generalized Time string */
	return LDB_SUCCESS;
}

/* NOTE: not a single attribute has this syntax in the basic w2k3 schema */
static int schema_validate_sensitive_string(struct ldb_context *ldb, struct ldb_val *val, int min, int max)
{
	/* TODO: find out what constitutes a "case sensitive string" */
	return LDB_SUCCESS;
}

static int schema_validate_unicode_string(struct ldb_context *ldb, struct ldb_val *val, int min, int max)
{
	/* TODO: validate utf8 string */
	return LDB_SUCCESS;
}

static int schema_validate_large_integer(struct ldb_context *ldb, struct ldb_val *val, int min, int max)
{
	/* TODO: validate large integer/interval */
	return LDB_SUCCESS;
}

static int schema_validate_object_sd(struct ldb_context *ldb, struct ldb_val *val, int min, int max)
{
	/* TODO: validate object Security Descriptor */
	return LDB_SUCCESS;
}

static int schema_validate_dn(struct ldb_context *ldb, struct ldb_val *val, int min, int max)
{
	struct ldb_dn *dn;
	int ret = LDB_SUCCESS;

	dn = ldb_dn_new(ldb, ldb, (const char *)val->data);
	if ( ! ldb_dn_validate(dn)) {
		ret = LDB_ERR_INVALID_ATTRIBUTE_SYNTAX;
	}

	talloc_free(dn);
	return ret;
}

static int schema_validate_binary_plus_dn(struct ldb_context *ldb, struct ldb_val *val, int min, int max)
{
	int ret = LDB_ERR_INVALID_ATTRIBUTE_SYNTAX;
	TALLOC_CTX *memctx;
	struct ldb_dn *dn;
	char *str, *p;
	char *endptr;
	int num;
       
	memctx = talloc_new(NULL);
	if (!memctx) return LDB_ERR_OPERATIONS_ERROR;

	str = talloc_strdup(memctx, (const char *)val->data);
	if (!str) {
		ret = LDB_ERR_OPERATIONS_ERROR;
		goto done;
	}
	if (strncasecmp(str, "B:", 2) != 0) {
		goto done;
	}

	/* point at the number of chars in the string */
	str = strchr(&str[2], ':');
	if (!str) {
		goto done;
	}
	str++;

	errno = 0;
	num = strtol(str, &endptr, 0);
	if (errno) return LDB_ERR_INVALID_ATTRIBUTE_SYNTAX;
	if (endptr[0] != ':') return LDB_ERR_INVALID_ATTRIBUTE_SYNTAX;
	if ((min > INT_MIN) && (num < min)) return LDB_ERR_INVALID_ATTRIBUTE_SYNTAX;
	if ((max < INT_MAX) && (num > max)) return LDB_ERR_INVALID_ATTRIBUTE_SYNTAX;

	/* point at the string */
	str = strchr(str, ':');
	if (!str) {
		goto done;
	}
	str++;

	/* terminate the string */
	p = strchr(str, ':');
	if (!p) {
		goto done;
	}
	*p = '\0';

	if (strlen(str) != 2*num) {
		goto done;
	}

	str = p + 1;

	dn = ldb_dn_new(memctx, ldb, str);
	if (ldb_dn_validate(dn)) {
		ret = LDB_SUCCESS;
	}

done:
	talloc_free(memctx);
	return ret;
}

static int schema_validate_x400_or_name(struct ldb_context *ldb, struct ldb_val *val, int min, int max)
{
	/* TODO: find out what is the syntax of an X400 OR NAME */
	return LDB_SUCCESS;
}

static int schema_validate_presentation_address(struct ldb_context *ldb, struct ldb_val *val, int min, int max)
{
	/* TODO: find out what is the syntax of a presentation address */
	return LDB_SUCCESS;
}

static int schema_validate_x400_access_point(struct ldb_context *ldb, struct ldb_val *val, int min, int max)
{
	/* TODO: find out what is the syntax of an X400 Access Point */
	return LDB_SUCCESS;
}

/* NOTE: seem there isn't a single attribute defined like this in the base w2k3 schema */
static int schema_validate_string_plus_dn(struct ldb_context *ldb, struct ldb_val *val, int min, int max)
{
	int ret = LDB_ERR_INVALID_ATTRIBUTE_SYNTAX;
	TALLOC_CTX *memctx;
	struct ldb_dn *dn;
	char *str, *p;
	char *endptr;
	int num;
       
	memctx = talloc_new(NULL);
	if (!memctx) return LDB_ERR_OPERATIONS_ERROR;

	str = talloc_strdup(memctx, (const char *)val->data);
	if (!str) {
		ret = LDB_ERR_OPERATIONS_ERROR;
		goto done;
	}
	if (strncasecmp(str, "S:", 2) != 0) {
		goto done;
	}

	/* point at the number of chars in the string */
	str = strchr(&str[2], ':');
	if (!str) {
		goto done;
	}
	str++;

	errno = 0;
	num = strtol(str, &endptr, 0);
	if (errno) return LDB_ERR_INVALID_ATTRIBUTE_SYNTAX;
	if (endptr[0] != ':') return LDB_ERR_INVALID_ATTRIBUTE_SYNTAX;
	if ((min > INT_MIN) && (num < min)) return LDB_ERR_INVALID_ATTRIBUTE_SYNTAX;
	if ((max < INT_MAX) && (num > max)) return LDB_ERR_INVALID_ATTRIBUTE_SYNTAX;

	/* point at the string */
	str = strchr(str, ':');
	if (!str) {
		goto done;
	}
	str++;

	/* terminate the string */
	p = strchr(str, ':');
	if (!p) {
		goto done;
	}
	*p = '\0';

	if (strlen(str) != num) {
		goto done;
	}

	str = p + 1;

	dn = ldb_dn_new(memctx, ldb, str);
	if (ldb_dn_validate(dn)) {
		ret = LDB_SUCCESS;
	}

done:
	talloc_free(memctx);
	return ret;
}

struct schema_syntax_validator {
	enum schema_internal_syntax type;
	int (*validate)(struct ldb_context *ldb, struct ldb_val *, int, int);
};

struct schema_syntax_validator schema_syntax_validators[] = {
	{ SCHEMA_AS_BOOLEAN, schema_validate_boolean },
	{ SCHEMA_AS_INTEGER, schema_validate_integer },
	{ SCHEMA_AS_OCTET_STRING, schema_validate_binary_blob },
	{ SCHEMA_AS_SID, schema_validate_sid },
	{ SCHEMA_AS_OID, schema_validate_oid },
	{ SCHEMA_AS_ENUMERATION, schema_validate_integer },
	{ SCHEMA_AS_NUMERIC_STRING, schema_validate_numeric_string },
	{ SCHEMA_AS_PRINTABLE_STRING, schema_validate_printable_string },
	{ SCHEMA_AS_CASE_IGNORE_STRING, schema_validate_teletext_string },
	{ SCHEMA_AS_IA5_STRING, schema_validate_ia5_string },
	{ SCHEMA_AS_UTC_TIME, schema_validate_utc_time },
	{ SCHEMA_AS_GENERALIZED_TIME, schema_validate_generalized_time },
	{ SCHEMA_AS_CASE_SENSITIVE_STRING, schema_validate_sensitive_string },
	{ SCHEMA_AS_DIRECTORY_STRING, schema_validate_unicode_string },
	{ SCHEMA_AS_LARGE_INTEGER, schema_validate_large_integer },
	{ SCHEMA_AS_OBJECT_SECURITY_DESCRIPTOR, schema_validate_object_sd },
	{ SCHEMA_AS_DN, schema_validate_dn },
	{ SCHEMA_AS_DN_BINARY, schema_validate_binary_plus_dn },
	{ SCHEMA_AS_OR_NAME, schema_validate_x400_or_name },
	{ SCHEMA_AS_REPLICA_LINK, schema_validate_binary_blob },
	{ SCHEMA_AS_PRESENTATION_ADDRESS, schema_validate_presentation_address }, /* see rfc1278 ? */
	{ SCHEMA_AS_ACCESS_POINT, schema_validate_x400_access_point },
	{ SCHEMA_AS_DN_STRING, schema_validate_string_plus_dn },
	{ -1, NULL }
};

int schema_validate(struct ldb_context *ldb,
		    struct ldb_message_element *el,
		    enum schema_internal_syntax type,
		    bool single, int min, int max)
{
	struct schema_syntax_validator *v;
	int i, ret;

	if (single && (el->num_values > 1)) {
		return LDB_ERR_INVALID_ATTRIBUTE_SYNTAX;
	}

	for (i = 0; schema_syntax_validators[i].type != 0; i++) {
		if (schema_syntax_validators[i].type == type)
			break;
	}
	if (schema_syntax_validators[i].type == 0) {
		return LDB_ERR_OPERATIONS_ERROR;
	}
	v = &schema_syntax_validators[i];
	
	for (i = 0; i < el->num_values; i++) {
		ret = v->validate(ldb, &el->values[i], min, max);
	}

	return LDB_SUCCESS;
}