#ifndef lint
static char *rcsid = "$Id: resconf.c,v 1.25 2001/04/16 02:25:17 m-kasahr Exp $";
#endif

/*
 * Copyright (c) 2000 Japan Network Information Center.  All rights reserved.
 *  
 * By using this file, you agree to the terms and conditions set forth bellow.
 * 
 * 			LICENSE TERMS AND CONDITIONS 
 * 
 * The following License Terms and Conditions apply, unless a different
 * license is obtained from Japan Network Information Center ("JPNIC"),
 * a Japanese association, Fuundo Bldg., 1-2 Kanda Ogawamachi, Chiyoda-ku,
 * Tokyo, Japan.
 * 
 * 1. Use, Modification and Redistribution (including distribution of any
 *    modified or derived work) in source and/or binary forms is permitted
 *    under this License Terms and Conditions.
 * 
 * 2. Redistribution of source code must retain the copyright notices as they
 *    appear in each source code file, this License Terms and Conditions.
 * 
 * 3. Redistribution in binary form must reproduce the Copyright Notice,
 *    this License Terms and Conditions, in the documentation and/or other
 *    materials provided with the distribution.  For the purposes of binary
 *    distribution the "Copyright Notice" refers to the following language:
 *    "Copyright (c) Japan Network Information Center.  All rights reserved."
 * 
 * 4. Neither the name of JPNIC may be used to endorse or promote products
 *    derived from this Software without specific prior written approval of
 *    JPNIC.
 * 
 * 5. Disclaimer/Limitation of Liability: THIS SOFTWARE IS PROVIDED BY JPNIC
 *    "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 JPNIC 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 DAMAGES.
 * 
 * 6. Indemnification by Licensee
 *    Any person or entities using and/or redistributing this Software under
 *    this License Terms and Conditions shall defend indemnify and hold
 *    harmless JPNIC from and against any and all judgements damages,
 *    expenses, settlement liabilities, cost and other liabilities of any
 *    kind as a result of use and redistribution of this Software or any
 *    claim, suite, action, litigation or proceeding by any third party
 *    arising out of or relates to this License Terms and Conditions.
 * 
 * 7. Governing Law, Jurisdiction and Venue
 *    This License Terms and Conditions shall be governed by and and
 *    construed in accordance with the law of Japan. Any person or entities
 *    using and/or redistributing this Software under this License Terms and
 *    Conditions hereby agrees and consent to the personal and exclusive
 *    jurisdiction and venue of Tokyo District Court of Japan.
 */

#include <config.h>

#include <stddef.h>
#include <stdarg.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>

#include <mdn/result.h>
#include <mdn/assert.h>
#include <mdn/logmacro.h>
#include <mdn/converter.h>
#include <mdn/normalizer.h>
#include <mdn/checker.h>
#include <mdn/mapper.h>
#include <mdn/mapselector.h>
#include <mdn/delimitermap.h>
#include <mdn/localencoding.h>
#include <mdn/resconf.h>
#include <mdn/debug.h>

#ifndef MDN_RESCONF_DIR
#define MDN_RESCONF_DIR		"/etc"
#endif
#define MDN_RESCONF_FILE	MDN_RESCONF_DIR "/mdn.conf"

#define MAX_CONF_LINE_LENGTH	255
#define MAX_CONF_LINE_ARGS	63

struct mdn_resconf {
	char *local_encoding;
	mdn_converter_t local_converter;
	mdn_converter_t idn_converter;
	mdn_converter_t alternate_converter;
	mdn_normalizer_t normalizer;
	mdn_checker_t prohibit_checker;
	mdn_checker_t unassigned_checker;
	mdn_mapper_t mapper;
	mdn_mapselector_t local_mapper;
	mdn_delimitermap_t delimiter_mapper;
	int reference_count;
};

static mdn_result_t	parse_conf(mdn_resconf_t ctx, FILE *fp);
static mdn_result_t	parse_alternate_encoding(mdn_resconf_t ctx,
						 char *args, int lineno);
static mdn_result_t	parse_delimiter_map(mdn_resconf_t ctx, char *args,
					    int lineno);
static mdn_result_t	parse_encoding_alias_file(mdn_resconf_t ctx,
						  char *args, int lineno);
static mdn_result_t	parse_idn_encoding(mdn_resconf_t ctx, char *args,
					   int lineno);
static mdn_result_t	parse_local_map(mdn_resconf_t ctx, char *args,
					int lineno);
static mdn_result_t	parse_nameprep(mdn_resconf_t ctx, char *args,
				       int lineno, char **nameprep);
static mdn_result_t	parse_map(mdn_resconf_t ctx, char *args, int lineno);
static mdn_result_t	parse_normalize(mdn_resconf_t ctx, char *args,
				        int lineno);
static mdn_result_t	parse_prohibit(mdn_resconf_t ctx, char *args,
				       int lineno);
static mdn_result_t	parse_unassigned(mdn_resconf_t ctx, char *args,
					 int lineno);
static int		split_args(char *s, char **av, int max_ac);
static void		resetconf(mdn_resconf_t ctx);
static mdn_result_t	update_local_converter(mdn_resconf_t ctx);
static mdn_result_t	mystrdup(const char *from, char **top);
static const char *	get_ucs(const char *p, unsigned long *vp);

mdn_result_t
mdn_resconf_initialize(void) {
	mdn_result_t r;

	TRACE(("mdn_resconf_initialize()\n"));

	/*
	 * Initialize sub modules.
	 */
	if ((r = mdn_converter_initialize()) != mdn_success)
		return (r);
	if ((r = mdn_normalizer_initialize()) != mdn_success)
		return (r);
	if ((r = mdn_checker_initialize()) != mdn_success)
		return (r);
	if ((r = mdn_mapselector_initialize()) != mdn_success)
		return (r);
	if ((r = mdn_mapper_initialize()) != mdn_success)
		return (r);

	return (mdn_success);
}

mdn_result_t
mdn_resconf_create(mdn_resconf_t *ctxp) {
	mdn_resconf_t ctx = NULL;

	assert(ctxp != NULL);

	TRACE(("mdn_resconf_create()\n"));

	if ((ctx = malloc(sizeof(*ctx))) == NULL)
		return (mdn_nomemory);

	ctx->local_encoding = NULL;
	ctx->local_converter = NULL;
	ctx->idn_converter = NULL;
	ctx->alternate_converter = NULL;
	ctx->normalizer = NULL;
	ctx->prohibit_checker = NULL;
	ctx->unassigned_checker = NULL;
	ctx->mapper = NULL;
	ctx->local_mapper = NULL;
	ctx->delimiter_mapper = NULL;
	ctx->reference_count = 1;

	*ctxp = ctx;

	return (mdn_success);
}

char *
mdn_resconf_defaultfile() {
	return MDN_RESCONF_FILE;
}

mdn_result_t
mdn_resconf_loadfile(mdn_resconf_t ctx, const char *file) {
	FILE *fp;
	mdn_result_t r;

	assert(ctx != NULL);

	TRACE(("mdn_resconf_loadfile(file=%s)\n",
	      file == NULL ? "<null>" : file));

	if ((r = mdn_converter_resetalias()) != mdn_success)
		return (r);

	resetconf(ctx);

	if (file == NULL)
		file = mdn_resconf_defaultfile();
	if ((fp = fopen(file, "r")) == NULL) {
		TRACE(("mdn_resconf_loadfile: cannot open %-.40s\n", file));
		return (mdn_nofile);
	}

	r = parse_conf(ctx, fp);
	fclose(fp);

	return (r);
}

void
mdn_resconf_destroy(mdn_resconf_t ctx) {
	TRACE(("mdn_resconf_destroy()\n"));
	TRACE(("mdn_resconf_destroy: update reference count (%d->%d)\n",
	    ctx->reference_count, ctx->reference_count - 1));

	ctx->reference_count--;
	if (ctx->reference_count <= 0) {
		TRACE(("mdn_converter_destroy: the object is destroyed\n"));
		resetconf(ctx);
		free(ctx);
	}
}

void
mdn_resconf_incrref(mdn_resconf_t ctx) {
	assert(ctx != NULL);

	TRACE(("mdn_resconf_incrref()\n"));
	TRACE(("mdn_resconf_incrref: update reference count (%d->%d)\n",
		ctx->reference_count, ctx->reference_count + 1));

	ctx->reference_count++;
}

mdn_converter_t
mdn_resconf_getalternateconverter(mdn_resconf_t ctx) {
	TRACE(("mdn_resconf_alternateconverter()\n"));

	if (ctx->alternate_converter != NULL)
		mdn_converter_incrref(ctx->alternate_converter);
	return (ctx->alternate_converter);
}

mdn_delimitermap_t
mdn_resconf_getdelimitermap(mdn_resconf_t ctx) {
	TRACE(("mdn_resconf_getdelimitermap()\n"));

	if (ctx->delimiter_mapper != NULL)
		mdn_delimitermap_incrref(ctx->delimiter_mapper);
	return (ctx->delimiter_mapper);
}

mdn_converter_t
mdn_resconf_getidnconverter(mdn_resconf_t ctx) {
	TRACE(("mdn_resconf_getidnconverter()\n"));

	if (ctx->idn_converter != NULL)
		mdn_converter_incrref(ctx->idn_converter);
	return (ctx->idn_converter);
}

mdn_converter_t
mdn_resconf_getlocalconverter(mdn_resconf_t ctx) {
	TRACE(("mdn_resconf_getlocalconverter()\n"));

	if (update_local_converter(ctx) != mdn_success)
		return (NULL);
	mdn_converter_incrref(ctx->local_converter);
	return (ctx->local_converter);
}

mdn_mapselector_t
mdn_resconf_getlocalmapselector(mdn_resconf_t ctx) {
	TRACE(("mdn_resconf_getlocalmapselector()\n"));

	if (ctx->local_mapper != NULL)
		mdn_mapselector_incrref(ctx->local_mapper);
	return (ctx->local_mapper);
}

mdn_mapper_t
mdn_resconf_getmapper(mdn_resconf_t ctx) {
	TRACE(("mdn_resconf_getmapper()\n"));

	if (ctx->mapper != NULL)
		mdn_mapper_incrref(ctx->mapper);
	return (ctx->mapper);
}

mdn_normalizer_t
mdn_resconf_getnormalizer(mdn_resconf_t ctx) {
	TRACE(("mdn_resconf_getnormalizer()\n"));

	if (ctx->normalizer != NULL)
		mdn_normalizer_incrref(ctx->normalizer);
	return (ctx->normalizer);
}

mdn_checker_t
mdn_resconf_getprohibitchecker(mdn_resconf_t ctx) {
	TRACE(("mdn_resconf_getprohibitchecker()\n"));

	if (ctx->prohibit_checker != NULL)
		mdn_checker_incrref(ctx->prohibit_checker);
	return (ctx->prohibit_checker);
}

mdn_checker_t
mdn_resconf_getunassignedchecker(mdn_resconf_t ctx) {
	TRACE(("mdn_resconf_getunassignedchecker()\n"));

	if (ctx->unassigned_checker != NULL)
		mdn_checker_incrref(ctx->unassigned_checker);
	return (ctx->unassigned_checker);
}

void
mdn_resconf_setalternateconverter(mdn_resconf_t ctx,
				  mdn_converter_t alternate_converter) {
	TRACE(("mdn_resconf_setalternateconverter()\n"));

	if (ctx->alternate_converter != NULL)
		mdn_converter_destroy(ctx->alternate_converter);
	ctx->alternate_converter = alternate_converter;
	mdn_converter_incrref(ctx->alternate_converter);
}

void
mdn_resconf_setdelimitermap(mdn_resconf_t ctx,
			    mdn_delimitermap_t delimiter_mapper) {
	TRACE(("mdn_resconf_setdelimitermap()\n"));
	if (ctx->delimiter_mapper != NULL)
		mdn_delimitermap_destroy(ctx->delimiter_mapper);
	ctx->delimiter_mapper = delimiter_mapper;
	mdn_delimitermap_incrref(ctx->delimiter_mapper);
}

void
mdn_resconf_setidnconverter(mdn_resconf_t ctx, 
			    mdn_converter_t idn_converter) {
	TRACE(("mdn_resconf_setidnconverter()\n"));

	if (ctx->idn_converter != NULL)
		mdn_converter_destroy(ctx->idn_converter);
	ctx->idn_converter = idn_converter;
	mdn_converter_incrref(ctx->idn_converter);
}

void
mdn_resconf_setlocalconverter(mdn_resconf_t ctx,
				  mdn_converter_t local_converter) {
	TRACE(("mdn_resconf_setlocalconverter()\n"));

	if (ctx->local_converter != NULL)
		mdn_converter_destroy(ctx->local_converter);
	free(ctx->local_encoding);
	ctx->local_encoding = NULL;	/* See update_local_converter(). */
	ctx->local_converter = local_converter;
	if (local_converter != NULL)
		mdn_converter_incrref(local_converter);
}

void
mdn_resconf_setlocalmapselector(mdn_resconf_t ctx,
				mdn_mapselector_t local_mapper) {
	TRACE(("mdn_resconf_setlocalmapselector()\n"));

	if (ctx->local_mapper != NULL)
		mdn_mapselector_destroy(ctx->local_mapper);
	ctx->local_mapper = local_mapper;
	mdn_mapselector_incrref(ctx->local_mapper);
}

void
mdn_resconf_setmapper(mdn_resconf_t ctx, mdn_mapper_t mapper) {
	TRACE(("mdn_resconf_setmapper()\n"));

	if (ctx->mapper != NULL)
		mdn_mapper_destroy(ctx->mapper);
	ctx->mapper = mapper;
	mdn_mapper_incrref(ctx->mapper);
}

void
mdn_resconf_setnormalizer(mdn_resconf_t ctx, mdn_normalizer_t normalizer) {
	TRACE(("mdn_resconf_setnormalizer()\n"));

	if (ctx->normalizer != NULL)
		mdn_normalizer_destroy(ctx->normalizer);
	ctx->normalizer = normalizer;
	mdn_normalizer_incrref(ctx->normalizer);
}

void
mdn_resconf_setprohibitchecker(mdn_resconf_t ctx,
			       mdn_checker_t prohibit_checker) {
	TRACE(("mdn_resconf_setprohibitchecker()\n"));

	if (ctx->prohibit_checker != NULL)
		mdn_checker_destroy(ctx->prohibit_checker);
	ctx->prohibit_checker = prohibit_checker;
	mdn_checker_incrref(ctx->prohibit_checker);
}

void
mdn_resconf_setunassignedchecker(mdn_resconf_t ctx,
				 mdn_checker_t unassigned_checker) {
	TRACE(("mdn_resconf_setunassignedchecker()\n"));
	if (ctx->unassigned_checker != NULL)
		mdn_checker_destroy(ctx->unassigned_checker);
	ctx->unassigned_checker = unassigned_checker;
	mdn_checker_incrref(ctx->unassigned_checker);
}

mdn_result_t
mdn_resconf_setnameprepversion(mdn_resconf_t ctx, const char *version)
{
	char prohibit_scheme_name[MAX_CONF_LINE_LENGTH + 1];
	char unassigned_scheme_name[MAX_CONF_LINE_LENGTH + 1];
	mdn_mapper_t mapper = NULL;
	mdn_normalizer_t normalizer = NULL;
	mdn_checker_t prohibit_checker = NULL;
	mdn_checker_t unassigned_checker = NULL;
	mdn_result_t r;

	assert(ctx != NULL && version != NULL);

	TRACE(("mdn_resconf_setnameprepversion()\n"));

	/*
	 * Set canonical scheme names.
	 */
	if (strlen(version) + strlen(MDN_CHECKER_PROHIBIT_PREFIX)
	    > MAX_CONF_LINE_LENGTH) {
		r = mdn_invalid_name;
		goto failure;
	}
	sprintf(prohibit_scheme_name, "%s%s",
	        MDN_CHECKER_PROHIBIT_PREFIX, version);

	if (strlen(version) + strlen(MDN_CHECKER_UNASSIGNED_PREFIX)
	    > MAX_CONF_LINE_LENGTH) {
		r = mdn_invalid_name;
		goto failure;
	}
	sprintf(unassigned_scheme_name, "%s%s",
	        MDN_CHECKER_UNASSIGNED_PREFIX, version);

	/*
	 * Create objects.
	 */
	r = mdn_mapper_create(&mapper);
	if (r != mdn_success)
		goto failure;
	r = mdn_normalizer_create(&normalizer);
	if (r != mdn_success)
		goto failure;
	r = mdn_checker_create(&prohibit_checker);
	if (r != mdn_success)
		goto failure;
	r = mdn_checker_create(&unassigned_checker);
	if (r != mdn_success)
		goto failure;

	r = mdn_mapper_add(mapper, version);
	if (r != mdn_success)
		goto failure;
	r = mdn_normalizer_add(normalizer, version);
	if (r != mdn_success)
		goto failure;
	r = mdn_checker_add(prohibit_checker, prohibit_scheme_name);
	if (r != mdn_success)
		goto failure;
	r = mdn_checker_add(unassigned_checker, unassigned_scheme_name);
	if (r != mdn_success)
		goto failure;

	/*
	 * Set the objects.
	 */
	mdn_resconf_setmapper(ctx, mapper);
	mdn_resconf_setnormalizer(ctx, normalizer);
	mdn_resconf_setprohibitchecker(ctx, prohibit_checker);
	mdn_resconf_setunassignedchecker(ctx, unassigned_checker);

	/*
	 * Destroy the objects.
	 */
	mdn_mapper_destroy(mapper);
	mdn_normalizer_destroy(normalizer);
	mdn_checker_destroy(prohibit_checker);
	mdn_checker_destroy(unassigned_checker);

	return (mdn_success);

failure:
	if (mapper != NULL)
		mdn_mapper_destroy(mapper);
	if (normalizer != NULL)
		mdn_normalizer_destroy(normalizer);
	if (prohibit_checker != NULL)
		mdn_checker_destroy(prohibit_checker);
	if (unassigned_checker != NULL)
		mdn_checker_destroy(unassigned_checker);

	return (r);
}

mdn_result_t
mdn_resconf_setalternateconvertername(mdn_resconf_t ctx, const char *name,
				      int flags) {
	mdn_converter_t alternate_converter;
	mdn_result_t r;

	assert(ctx != NULL && name != NULL);

	TRACE(("mdn_resconf_setalternateconvertername(name=%s, flags=%d)\n",
	      name, flags));

	r = mdn_converter_create(name, &alternate_converter, flags);
	if (r != mdn_success)
		return (r);

	if (!mdn_converter_isasciicompatible(alternate_converter)) {
		mdn_converter_destroy(alternate_converter);
		return (mdn_invalid_name);
	}

	if (ctx->alternate_converter != NULL)
		mdn_converter_destroy(ctx->alternate_converter);
	ctx->alternate_converter = alternate_converter;

	return (mdn_success);
}

mdn_result_t
mdn_resconf_setidnconvertername(mdn_resconf_t ctx, const char *name,
				int flags) {
	mdn_converter_t idn_converter;
	mdn_result_t r;

	assert(ctx != NULL && name != NULL);

	TRACE(("mdn_resconf_setidnconvertername(name=%s, flags=%d)\n",
	      name, flags));

	r = mdn_converter_create(name, &idn_converter, flags);
	if (r != mdn_success)
		return (r);

	if (ctx->idn_converter != NULL)
		mdn_converter_destroy(ctx->idn_converter);
	ctx->idn_converter = idn_converter;

	return (mdn_success);
}

mdn_result_t
mdn_resconf_setlocalconvertername(mdn_resconf_t ctx, const char *name,
				  int flags) {
	mdn_converter_t local_converter;
	char *local_encoding = NULL;
	mdn_result_t r;

	assert(ctx != NULL);

	TRACE(("mdn_resconf_setlocalconvertername(mame=%s, flags=%d)\n",
	      name == NULL ? "<null>" : name, flags));

	if (name == NULL) {
		local_converter = NULL;
	} else {
		r = mdn_converter_create(name, &local_converter, flags);
		if (r != mdn_success) {
			free(local_encoding);
			return (r);
		}
	}
	if (ctx->local_converter != NULL)
		mdn_converter_destroy(ctx->local_converter);
	free(ctx->local_encoding);
	ctx->local_converter = local_converter;
	ctx->local_encoding = NULL;	/* See update_local_converter(). */

	return (mdn_success);
}

mdn_result_t
mdn_resconf_addalldelimitermapucs(mdn_resconf_t ctx, unsigned long *v,
				  int nv) {
	mdn_result_t r;

	TRACE(("mdn_resconf_addalldelimitermapucs(nv=%d)\n", nv));

	if (ctx->delimiter_mapper == NULL) {
		r = mdn_delimitermap_create(&(ctx->delimiter_mapper));
		if (r != mdn_success)
			return (r);
	}

	r = mdn_delimitermap_addall(ctx->delimiter_mapper, v, nv);
	return (r);
}

mdn_result_t
mdn_resconf_addalllocalmapselectornames(mdn_resconf_t ctx, const char *tld,
					const char **names, int nnames) {
	mdn_result_t r;

	assert(ctx != NULL && names != NULL);

	TRACE(("mdn_resconf_addalllocalmapselectorname(tld=%s, nnames=%d)\n",
	      tld, nnames));

	if (ctx->local_mapper == NULL) {
		r = mdn_mapselector_create(&(ctx->local_mapper));
		if (r != mdn_success)
			return (r);
	}

	r = mdn_mapselector_addall(ctx->local_mapper, tld, names, nnames);
	return (r);
}

mdn_result_t
mdn_resconf_addallmappernames(mdn_resconf_t ctx, const char **names,
			      int nnames) {
	mdn_result_t r;

	assert(ctx != NULL && names != NULL);

	TRACE(("mdn_resconf_addallmappername()\n"));

	if (ctx->mapper == NULL) {
		r = mdn_mapper_create(&(ctx->mapper));
		if (r != mdn_success)
			return (r);
	}

	r = mdn_mapper_addall(ctx->mapper, names, nnames);
	return (r);
}

mdn_result_t
mdn_resconf_addallnormalizernames(mdn_resconf_t ctx, const char **names,
				  int nnames) {
	mdn_result_t r;

	assert(ctx != NULL && names != NULL);

	TRACE(("mdn_resconf_addallnormalizername(nnames=%d)\n", nnames));

	if (ctx->normalizer == NULL) {
		r = mdn_normalizer_create(&(ctx->normalizer));
		if (r != mdn_success)
			return (r);
	}

	r = mdn_normalizer_addall(ctx->normalizer, names, nnames);
	return (r);
}

mdn_result_t
mdn_resconf_addallprohibitcheckernames(mdn_resconf_t ctx, const char **names,
				       int nnames) {
	char long_name[MAX_CONF_LINE_LENGTH + 1];
	mdn_result_t r;
	int i;

	assert(ctx != NULL && names != NULL);

	TRACE(("mdn_resconf_addallprohibitcheckername(nnames=%d)\n", nnames));

	if (ctx->prohibit_checker == NULL) {
		r = mdn_checker_create(&(ctx->prohibit_checker));
		if (r != mdn_success)
			return (r);
	}

	for (i = 0; i < nnames; i++, names++) {
		if (strlen(*names) + strlen(MDN_CHECKER_PROHIBIT_PREFIX)
			> MAX_CONF_LINE_LENGTH) {
			return (mdn_invalid_name);
		}
		strcpy(long_name, MDN_CHECKER_PROHIBIT_PREFIX);
		strcat(long_name, *names);

		r = mdn_checker_add(ctx->prohibit_checker, long_name);
		if (r != mdn_success)
			return (r);
	}

	return (mdn_success);
}

mdn_result_t
mdn_resconf_addallunassignedcheckernames(mdn_resconf_t ctx, const char **names,
					 int nnames) {
	char long_name[MAX_CONF_LINE_LENGTH + 1];
	mdn_result_t r;
	int i;

	assert(ctx != NULL && names != NULL);

	TRACE(("mdn_resconf_addallunassignedcheckername(nnames=%d)\n",
	      nnames));

	if (ctx->unassigned_checker == NULL) {
		r = mdn_checker_create(&(ctx->unassigned_checker));
		if (r != mdn_success)
			return (r);
	}

	for (i = 0; i < nnames; i++, names++) {
		if (strlen(*names) + strlen(MDN_CHECKER_UNASSIGNED_PREFIX)
			> MAX_CONF_LINE_LENGTH) {
			return (mdn_invalid_name);
		}
		strcpy(long_name, MDN_CHECKER_UNASSIGNED_PREFIX);
		strcat(long_name, *names);

		r = mdn_checker_add(ctx->unassigned_checker, long_name);
		if (r != mdn_success)
			return (r);
	}

	return (mdn_success);
}

static mdn_result_t
parse_conf(mdn_resconf_t ctx, FILE *fp) {
	char line[MAX_CONF_LINE_LENGTH + 1];
	int lineno = 0;
	char *argv[3];
	int argc;
	mdn_result_t r;
	char *alternate_encoding_args = NULL;
	int alternate_encoding_lineno = 0;
	char *idn_encoding_args = NULL;
	int idn_encoding_lineno = 0;
	char *nameprep = NULL;

	/*
	 * Parse config file.  parsing of 'alternate-encoding' and
	 * 'idn-encoding' lines are postponed because
	 * 'alias-file' line must be processed before them.
	 */
	while (fgets(line, sizeof(line), fp) != NULL) {
		char *newline;

		lineno++;
		newline = strpbrk(line, "\r\n");
		if (newline != NULL)
			*newline = '\0';
		else if (fgetc(fp) != EOF) {
			mdn_log_error("mdnres: too long line \"%-.30s\", "
				      "line %d", line, lineno);
			return (mdn_invalid_syntax);
		}

		argc = split_args(line, argv, 2);
		if (argc == -1) {
			mdn_log_error("mdnres: syntax error, line %d\n",
				lineno);
			return (mdn_invalid_syntax);
		} else if (argc == 0 || argv[0][0] == '#') {
			continue;
		}

		if (strcmp(argv[0], "alternate-encoding") == 0) {
			r = mystrdup(argv[1], &alternate_encoding_args);
			if (r != mdn_success) {
				mdn_log_error("mdnres: %s, line %d\n",
					      mdn_result_tostring(r), lineno);
			}
			alternate_encoding_lineno = lineno;

		} else if (strcmp(argv[0], "delimiter-map") == 0) {
			r = parse_delimiter_map(ctx, argv[1], lineno);

		} else if (strcmp(argv[0], "encoding-alias-file") == 0) {
			r = parse_encoding_alias_file(ctx, argv[1], lineno);

		} else if (strcmp(argv[0], "idn-encoding") == 0) {
			r = mystrdup(argv[1], &idn_encoding_args);
			if (r != mdn_success) {
				mdn_log_error("mdnres: %s, line %d\n",
					      mdn_result_tostring(r), lineno);
			}
			idn_encoding_lineno = lineno;

		} else if (strcmp(argv[0], "local-map") == 0) {
			r = parse_local_map(ctx, argv[1], lineno);

		} else if (strcmp(argv[0], "nameprep") == 0) {
			r = parse_nameprep(ctx, argv[1], lineno, &nameprep);

		} else if (strcmp(argv[0], "nameprep-map") == 0) {
			r = parse_map(ctx, argv[1], lineno);

		} else if (strcmp(argv[0], "nameprep-normalize") == 0) {
			r = parse_normalize(ctx, argv[1], lineno);

		} else if (strcmp(argv[0], "nameprep-prohibit") == 0) {
			r = parse_prohibit(ctx, argv[1], lineno);

		} else if (strcmp(argv[0], "nameprep-unassigned") == 0) {
			r = parse_unassigned(ctx, argv[1], lineno);

		} else if (strcmp(argv[0], "server-zld") == 0 || 
			strcmp(argv[0], "alias-file") == 0 ||
			strcmp(argv[0], "normalize") == 0 ||
			strcmp(argv[0], "server-encoding") == 0) {
			mdn_log_warning("mdnres: obsolete command "
					"\"%-.30s\", line %d (ignored)\n",
					argv[0], lineno);
			r = mdn_success;
		} else {
			mdn_log_error("mdnres: unrecognized command "
				      "\"%-.30s\", line %d\n",
				      argv[0], lineno);
			r = mdn_invalid_syntax;
		}
		if (r != mdn_success)
			return (r);
	}

	lineno++;

	if (nameprep != NULL) {
		if (ctx->mapper == NULL) {
			r = parse_map(ctx, nameprep, lineno);
			if (r != mdn_success)
				return (r);
		}
		if (ctx->normalizer == NULL) {
			r = parse_normalize(ctx, nameprep, lineno);
			if (r != mdn_success)
				return (r);
		}
		if (ctx->prohibit_checker == NULL) {
			r = parse_prohibit(ctx, nameprep, lineno);
			if (r != mdn_success)
				return (r);
		}
		if (ctx->unassigned_checker == NULL) {
			r = parse_unassigned(ctx, nameprep, lineno);
			if (r != mdn_success)
				return (r);
		}
	}

	if (alternate_encoding_args != NULL) {
		r = parse_alternate_encoding(ctx, alternate_encoding_args,
					     alternate_encoding_lineno);
		if (r != mdn_success)
			return (r);
	}
	if (idn_encoding_args != NULL) {
		r = parse_idn_encoding(ctx, idn_encoding_args,
				       idn_encoding_lineno);
		if (r != mdn_success)
			return (r);
	}

	return (mdn_success);
}

static mdn_result_t
parse_alternate_encoding(mdn_resconf_t ctx, char *args, int lineno) {
	mdn_result_t r;
	char *argv[MAX_CONF_LINE_ARGS + 1];
	int argc;

	argc = split_args(args, argv, MAX_CONF_LINE_ARGS + 1);

	if (argc != 1) {
		mdn_log_error("mdnres: wrong # of args for "
			      "alternate-encoding, line %d\n", lineno);
		return (mdn_invalid_syntax);
	}

	r = mdn_converter_create(argv[0], &ctx->alternate_converter,
				 MDN_CONVERTER_DELAYEDOPEN);
	if (r != mdn_success) {
		mdn_log_error("mdnres: cannot create alternate-encodng "
			      "converter, %s, line %d\n", 
			      mdn_result_tostring(r), lineno);
		return (mdn_invalid_syntax);
	}

	if (!mdn_converter_isasciicompatible(ctx->alternate_converter)) {
		mdn_log_error("mdnres: alternate encoding must be "
			      "ASCII-compatible, line %d\n", lineno);
		mdn_converter_destroy(ctx->alternate_converter);
		ctx->alternate_converter = NULL;
		return (mdn_invalid_name);
	}

	return (r);
}

static mdn_result_t
parse_delimiter_map(mdn_resconf_t ctx, char *args, int lineno) {
	mdn_result_t r;
	char *argv[MAX_CONF_LINE_ARGS + 1];
	int argc;
	unsigned long ucs;
	int i;

	argc = split_args(args, argv, MAX_CONF_LINE_ARGS + 1);

	if (argc > MAX_CONF_LINE_ARGS) {
		mdn_log_error("mdnres: wrong # of args for delimiter-map, "
			      "line %d\n", lineno);
		return (mdn_invalid_syntax);
	}

	if (ctx->delimiter_mapper == NULL) {
		r = mdn_delimitermap_create(&ctx->delimiter_mapper);
		if (r != mdn_success) {
			mdn_log_error("mdnres: cannot create delimiter "
				      "mapper, %s, line %d\n", 
				      mdn_result_tostring(r), lineno);
			return (r);
		}
	}

	for (i = 0; i < argc; i++) {
		if (get_ucs(argv[i], &ucs) == NULL) {
			mdn_log_error("mdnres: invalid delimiter "
				      "\"%-.30s\", line %d\n",
				      argv[i], lineno);
			return (mdn_invalid_syntax);
		}
		r = mdn_delimitermap_add(ctx->delimiter_mapper, ucs);
		if (r == mdn_invalid_codepoint) {
			mdn_log_error("mdnres: invalid delimiter "
				      "\"%-.30s\", line %d\n",
				      argv[i], lineno);
			return (r);
		} else if (r != mdn_success) {
			return (r);
		}
	}

	return (mdn_success);
}

static mdn_result_t
parse_encoding_alias_file(mdn_resconf_t ctx, char *args, int lineno) {
	mdn_result_t r;
	char *argv[MAX_CONF_LINE_ARGS + 1];
	int argc;

	argc = split_args(args, argv, MAX_CONF_LINE_ARGS + 1);

	if (argc != 1) {
		mdn_log_error("mdnres: wrong # of args for "
			      "encoding-alias-file, line %d\n", lineno);
		return (mdn_invalid_syntax);
	}

	r = mdn_converter_aliasfile(argv[0]);
	if (r != mdn_success) {
		mdn_log_error("mdnres: cannot set aliasfile, %s, line %d\n",
			      mdn_result_tostring(r), lineno);
	}

	return (r);
}

static mdn_result_t
parse_idn_encoding(mdn_resconf_t ctx, char *args, int lineno) {
	mdn_result_t r;
	char *argv[MAX_CONF_LINE_ARGS + 1];
	int argc;

	argc = split_args(args, argv, MAX_CONF_LINE_ARGS + 1);

	if (argc != 1) {
		mdn_log_error("mdnres: wrong # of args for idn-encoding, "
			      "line %d\n", lineno);
		return (mdn_invalid_syntax);
	}

	r = mdn_converter_create(argv[0], &ctx->idn_converter,
				 MDN_CONVERTER_DELAYEDOPEN);
	if (r != mdn_success) {
		mdn_log_error("mdnres: cannot create idn converter, %s, "
			      "line %d\n", mdn_result_tostring(r), lineno);
	}

	return (r);
}

static mdn_result_t
parse_local_map(mdn_resconf_t ctx, char *args, int lineno) {
	mdn_result_t r;
	char *argv[MAX_CONF_LINE_ARGS + 1];
	int argc;
	int i;

	argc = split_args(args, argv, MAX_CONF_LINE_ARGS + 1);

	if (argc < 2 || argc > MAX_CONF_LINE_ARGS) {
		mdn_log_error("mdnres: wrong # of args for local-map, "
			      "line %d\n", lineno);
		return (mdn_invalid_syntax);
	}

	if (ctx->local_mapper == NULL) {
		r = mdn_mapselector_create(&ctx->local_mapper);
		if (r != mdn_success) {
			mdn_log_error("mdnres: cannot create local mapper, "
				      "%s, line %d\n",
				      mdn_result_tostring(r), lineno);
			return (r);
		}
	}

	for (i = 1; i < argc; i++) {
		r = mdn_mapselector_add(ctx->local_mapper, argv[0], argv[i]);
		if (r == mdn_invalid_name) {
			mdn_log_error("mdnres: map scheme unavailable "
				      "\"%-.30s\", line %d\n",
				      argv[i], lineno);
			return (r);
		} else if (r != mdn_success) {
			return (r);
		}
	}

	return (mdn_success);
}

static mdn_result_t
parse_nameprep(mdn_resconf_t ctx, char *args, int lineno, char **nameprep) {
	mdn_result_t r;
	char *argv[MAX_CONF_LINE_ARGS + 1];
	int argc;

	argc = split_args(args, argv, MAX_CONF_LINE_ARGS + 1);

	if (argc != 1) {
		mdn_log_error("mdnres: wrong # of args for nameprep, "
			      "line %d\n", lineno);
		return (mdn_invalid_syntax);
	}

	r = mystrdup(argv[0], nameprep);
	if (r != mdn_success) {
		mdn_log_error("mdnres: cannot set nameprep, %s, line %d\n",
			      mdn_result_tostring(r), lineno);
	}
	
	return (r);
}

static mdn_result_t
parse_map(mdn_resconf_t ctx, char *args, int lineno) {
	mdn_result_t r;
	char *argv[MAX_CONF_LINE_ARGS + 1];
	int argc;
	int i;

	argc = split_args(args, argv, MAX_CONF_LINE_ARGS + 1);

	if (argc > MAX_CONF_LINE_ARGS) {
		mdn_log_error("mdnres: wrong # of args for map, line %d\n",
			      lineno);
		return (mdn_invalid_syntax);
	}

	if (ctx->mapper == NULL) {
		r = mdn_mapper_create(&ctx->mapper);
		if (r != mdn_success) {
			mdn_log_error("mdnres: cannot create mapper, %s, "
				      "line %d\n", mdn_result_tostring(r),
				      lineno);
			return (r);
		}
	}

	for (i = 0; i < argc; i++) {
		r = mdn_mapper_add(ctx->mapper, argv[i]);
		if (r == mdn_invalid_name) {
			mdn_log_error("mdnres: map scheme unavailable "
				      "\"%-.30s\", line %d\n",
				      argv[i], lineno);
			return (r);
		} else if (r != mdn_success) {
			return (r);
		}
	}

	return (mdn_success);
}

static mdn_result_t
parse_normalize(mdn_resconf_t ctx, char *args, int lineno) {
	mdn_result_t r;
	char *argv[MAX_CONF_LINE_ARGS + 1];
	int argc;
	int i;

	argc = split_args(args, argv, MAX_CONF_LINE_ARGS + 1);

	if (argc > MAX_CONF_LINE_ARGS) {
		mdn_log_error("mdnres: wrong # of args for normalize, "
			      "line %d\n", lineno);
		return (mdn_invalid_syntax);
	}

	if (ctx->normalizer == NULL) {
		r = mdn_normalizer_create(&ctx->normalizer);
		if (r != mdn_success) {
			mdn_log_error("mdnres: cannot create normalizer, %s, "
				      "line %d\n", mdn_result_tostring(r),
				      lineno);
			return (r);
		}
	}

	for (i = 0; i < argc; i++) {
		r = mdn_normalizer_add(ctx->normalizer, argv[i]);
		if (r == mdn_invalid_name) {
			mdn_log_error("mdnres: unknown normalization scheme "
				      "\"%-.30s\", line %d\n",
				      argv[i], lineno);
			return (r);
		} else if (r != mdn_success) {
			return (r);
		}
	}

	return (mdn_success);
}

static mdn_result_t
parse_prohibit(mdn_resconf_t ctx, char *args, int lineno) {
	mdn_result_t r;
	char *argv[MAX_CONF_LINE_ARGS + 1];
	int argc;
	char scheme_name[MAX_CONF_LINE_LENGTH + 1];
	int i;

	argc = split_args(args, argv, MAX_CONF_LINE_ARGS + 1);

	if (argc > MAX_CONF_LINE_ARGS) {
		mdn_log_error("mdnres: wrong # of args for prohibit, "
			      "line %d\n", lineno);
		return (mdn_invalid_syntax);
	}

	if (ctx->prohibit_checker == NULL) {
		r = mdn_checker_create(&ctx->prohibit_checker);
		if (r != mdn_success) {
			mdn_log_error("mdnres: cannot create prohibit "
				      "checker, %s, line %d\n",
				      mdn_result_tostring(r), lineno);
			return (r);
		}
	}

	for (i = 0; i < argc; i++) {
		sprintf(scheme_name, "%s%s", MDN_CHECKER_PROHIBIT_PREFIX,
			argv[i]);
		r = mdn_checker_add(ctx->prohibit_checker, scheme_name);
		if (r == mdn_invalid_name) {
			mdn_log_error("mdnres: prohibit scheme unavailable "
				      "\"%-.30s\", line %d\n",
				      argv[i], lineno);
			return (r);
		} else if (r != mdn_success) {
			return (r);
		}
	}

	return (mdn_success);
}

static mdn_result_t
parse_unassigned(mdn_resconf_t ctx, char *args, int lineno) {
	mdn_result_t r;
	char *argv[MAX_CONF_LINE_ARGS + 1];
	int argc;
	char scheme_name[MAX_CONF_LINE_LENGTH + 1];
	int i;

	argc = split_args(args, argv, MAX_CONF_LINE_ARGS + 1);

	if (argc > MAX_CONF_LINE_ARGS) {
		mdn_log_error("mdnres: wrong # of args for unassigned, "
			      "line %d\n", lineno);
		return (mdn_invalid_syntax);
	}

	if (ctx->unassigned_checker == NULL) {
		r = mdn_checker_create(&ctx->unassigned_checker);
		if (r != mdn_success) {
			mdn_log_error("mdnres: cannot create unassigned "
				      "checker, %s, line %d\n",
				      mdn_result_tostring(r), lineno);
			return (r);
		}
	}

	for (i = 0; i < argc; i++) {
		sprintf(scheme_name, "%s%s", MDN_CHECKER_UNASSIGNED_PREFIX,
			argv[i]);
		r = mdn_checker_add(ctx->unassigned_checker, scheme_name);
		if (r == mdn_invalid_name) {
			mdn_log_error("mdnres: unassigned scheme unavailable "
				      "\"%-.30s\", line %d\n",
				      argv[i], lineno);
			return (r);
		} else if (r != mdn_success) {
			return (r);
		}
	}

	return (mdn_success);
}

static int
split_args(char *s, char **av, int max_ac) {
	int ac;
	int i;

	for (ac = 0; *s != '\0' && ac < max_ac; ac++) {
		if (ac > 0)
			*s++ = '\0';
		while (isspace((unsigned char)*s))
			s++;
		if (*s == '\0')
			break;
		if (*s == '"' || *s == '\'') {
			int qc = *s++;
			av[ac] = s;
			while (*s != qc) {
				if (*s == '\0')
					return (-1);
				s++;
			}
		} else {
			av[ac] = s;
			while (*s != '\0' && !isspace((unsigned char)*s))
				s++;
		}
	}

	for (i = ac; i < max_ac; i++)
		av[i] = NULL;

	return (ac);
}

static void
resetconf(mdn_resconf_t ctx) {
	free(ctx->local_encoding);
	ctx->local_encoding = NULL;

	if (ctx->local_converter != NULL) {
		mdn_converter_destroy(ctx->local_converter);
		ctx->local_converter = NULL;
	}
	if (ctx->idn_converter != NULL) {
		mdn_converter_destroy(ctx->idn_converter);
		ctx->idn_converter = NULL;
	}
	if (ctx->alternate_converter != NULL) {
		mdn_converter_destroy(ctx->alternate_converter);
		ctx->alternate_converter = NULL;
	}
	if (ctx->normalizer != NULL) {
		mdn_normalizer_destroy(ctx->normalizer);
		ctx->normalizer = NULL;
	}
	if (ctx->prohibit_checker != NULL) {
		mdn_checker_destroy(ctx->prohibit_checker);
		ctx->prohibit_checker = NULL;
	}
	if (ctx->unassigned_checker != NULL) {
		mdn_checker_destroy(ctx->unassigned_checker);
		ctx->unassigned_checker = NULL;
	}
	if (ctx->mapper != NULL) {
		mdn_mapper_destroy(ctx->mapper);
		ctx->mapper = NULL;
	}
	if (ctx->local_mapper != NULL) {
		mdn_mapselector_destroy(ctx->local_mapper);
		ctx->local_mapper = NULL;
	}
	if (ctx->delimiter_mapper != NULL) {
		mdn_delimitermap_destroy(ctx->delimiter_mapper);
		ctx->delimiter_mapper = NULL;
	}
}

static mdn_result_t
update_local_converter(mdn_resconf_t ctx) {
	mdn_result_t r;
	const char *new_local_encoding;

	/*
	 * This condition comes true only when the converter is set by
	 * mdn_resconf_setlocalconverter().  In this case, we don't
	 * update the local converter.
	 */
	if (ctx->local_encoding == NULL && ctx->local_converter != NULL)
		return (mdn_success);

	/*
	 * Update the local converer if the local encoding is changed.
	 */
	new_local_encoding = mdn_localencoding_name();
	if (new_local_encoding == NULL) {
		mdn_log_error("cannot determine local codeset name\n");
		return (mdn_notfound);
	}

	if (ctx->local_encoding != NULL &&
	    ctx->local_converter != NULL &&
	    strcmp(ctx->local_encoding, new_local_encoding) == 0) {
		return (mdn_success);
	}

	free(ctx->local_encoding);
	ctx->local_encoding = NULL;
	if (ctx->local_converter != NULL) {
		mdn_converter_destroy(ctx->local_converter);
		ctx->local_converter = NULL;
	}

	r = mystrdup(new_local_encoding, &ctx->local_encoding);
	if (r != mdn_success)
		return (r);
	r = mdn_converter_create(ctx->local_encoding,
				 &ctx->local_converter,
				 MDN_CONVERTER_RTCHECK);
	return (r);
}

static mdn_result_t
mystrdup(const char *from, char **top) {
	char *s;

	if (*top != NULL) {
		free(*top);
		*top = NULL;
	}

	if (from == NULL)
		from = "";
	s = malloc(strlen(from) + 1);
	if (s == NULL)
		return (mdn_nomemory);
	(void)strcpy(s, from);
	*top = s;
	return (mdn_success);
}

static const char *
get_ucs(const char *p, unsigned long *vp) {
	char *end;

	/* Skip leading space */
	while (isspace((unsigned char)*p))
		p++;

	/* Skip optional 'U+' */
	if (strncmp(p, "U+", 2) == 0)
		p += 2;

	*vp = strtoul(p, &end, 16);
	if (end == p) {
		return (NULL);
	}
	p = end;

	/* Skip trailing space */
	while (isspace((unsigned char)*p))
		p++;
	return p;
}

#ifdef TEST

int
main(int argc, char *argv[])
{
	mdn_resconf_t resconf;
	mdn_result_t r;
	char *conf_file;

	if (argc == 1)
		conf_file = mdn_resconf_defaultfile();
	else
		conf_file = argv[1];

	r = mdn_resconf_initialize();
	if (r != mdn_success) {
		fprintf(stderr, "%s: %s\n", argv[0], mdn_result_tostring(r));
		exit(1);
	}

	r = mdn_resconf_create(&resconf);
	if (r != mdn_success) {
		fprintf(stderr, "%s: %s\n", argv[0], mdn_result_tostring(r));
		exit(1);
	}

	r = mdn_resconf_loadfile(resconf, conf_file);
	if (r != mdn_success) {
		fprintf(stderr, "%s: %s, %s\n", argv[0],
			mdn_result_tostring(r), conf_file);
		exit(1);
	}

	mdn_resconf_destroy(resconf);

	return 0;
}

#endif /* TEST */
