/*
 * Initialize Cards according to PKCS#15
 *
 * Copyright (C) 2002 Olaf Kirch <okir@suse.de>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 *
 * Random notes
 *  -	the "key" command should go away, it's obsolete
 */

#include "config.h"

#include <stdio.h>
#include <ctype.h>
#include <stdarg.h>
#include <string.h>
#include <limits.h>
#ifdef HAVE_STRINGS_H
#include <strings.h>
#endif
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <assert.h>
#include <stdlib.h>

#ifdef _WIN32
#include <windows.h>
#include <winreg.h>
#endif

#include "common/compat_strlcpy.h"
#include "common/libscdl.h"
#include "libopensc/log.h"
#include "libopensc/pkcs15.h"
#include "pkcs15-init.h"
#include "profile.h"
#include "scconf/scconf.h"

#define DEF_PRKEY_RSA_ACCESS	0x1D
#define DEF_PUBKEY_ACCESS	0x12

#define TEMPLATE_FILEID_MIN_DIFF	0x20

#define WORD_SIZE	64

/*
#define DEBUG_PROFILE
*/

/*
 * Parser state
 */
struct state {
	struct state *		frame;
	const char *		filename;
	struct sc_profile *	profile;
	struct file_info *	file;
	struct pin_info *	pin;
	struct auth_info *	key;
};


struct command {
	const char *		name;
	int			min_args, max_args;
	int			(*func)(struct state *, int, char **);
};

struct block {
	const char *		name;
	int			(*handler)(struct state *,
					struct block *,
					const char *,
					scconf_block *);
	struct command *	cmd_info;
	struct block *		blk_info;
};

struct map {
	const char *		name;
	unsigned int		val;
};

static struct map		aclNames[] = {
	{ "NONE",	SC_AC_NONE	},
	{ "NEVER",	SC_AC_NEVER	},
	{ "CHV",	SC_AC_CHV	},
	{ "TERM",	SC_AC_TERM	},
	{ "PRO",	SC_AC_PRO	},
	{ "AUT",	SC_AC_AUT	},
	{ "KEY",	SC_AC_AUT	},
	{ "SEN",	SC_AC_SEN	},
	{ "IDA",	SC_AC_IDA	},
	{ "SCB",	SC_AC_SCB	},
	{ NULL, 0 }
};
static struct map		fileOpNames[] = {
	{ "SELECT",	SC_AC_OP_SELECT	},
	{ "LOCK",	SC_AC_OP_LOCK	},
	{ "DELETE",	SC_AC_OP_DELETE	},
	{ "DELETE-SELF",SC_AC_OP_DELETE_SELF },
	{ "CREATE",	SC_AC_OP_CREATE	},
	{ "CREATE-EF",	SC_AC_OP_CREATE_EF	},
	{ "CREATE-DF",	SC_AC_OP_CREATE_DF	},
	{ "REHABILITATE",SC_AC_OP_REHABILITATE	},
	{ "INVALIDATE",	SC_AC_OP_INVALIDATE	},
	{ "FILES",	SC_AC_OP_LIST_FILES	},
	{ "READ",	SC_AC_OP_READ	},
	{ "UPDATE",	SC_AC_OP_UPDATE	},
	{ "WRITE",	SC_AC_OP_WRITE	},
	{ "ERASE",	SC_AC_OP_ERASE	},
	{ "CRYPTO",     SC_AC_OP_CRYPTO },
        { "PIN-DEFINE", SC_AC_OP_PIN_DEFINE },
        { "PIN-CHANGE", SC_AC_OP_PIN_CHANGE },
        { "PIN-RESET",  SC_AC_OP_PIN_RESET },
        { "PIN-USE",	SC_AC_OP_PIN_USE },
	{ "GENERATE",	SC_AC_OP_GENERATE },
	{ "PSO-COMPUTE-SIGNATURE",	SC_AC_OP_PSO_COMPUTE_SIGNATURE },
	{ "INTERNAL-AUTHENTICATE",	SC_AC_OP_INTERNAL_AUTHENTICATE },
	{ "PSO-DECRYPT",		SC_AC_OP_PSO_DECRYPT },
	{ "RESIZE",	SC_AC_OP_RESIZE },
	{ "ADMIN",	SC_AC_OP_ADMIN	},
	{ "ACTIVATE",	SC_AC_OP_ACTIVATE },
	{ "DEACTIVATE",	SC_AC_OP_DEACTIVATE },
	{ NULL, 0 }
};
static struct map		fileTypeNames[] = {
	{ "EF",		SC_FILE_TYPE_WORKING_EF		},
	{ "INTERNAL-EF",SC_FILE_TYPE_INTERNAL_EF	},
	{ "DF",		SC_FILE_TYPE_DF			},
	{ "BSO",	SC_FILE_TYPE_BSO		},
	{ NULL, 0 }
};
static struct map		fileStructureNames[] = {
	{ "TRANSPARENT",	SC_FILE_EF_TRANSPARENT	},
	{ "LINEAR-FIXED",	SC_FILE_EF_LINEAR_FIXED	},
	{ "LINEAR-FIXED-TLV",	SC_FILE_EF_LINEAR_FIXED_TLV	},
	{ "LINEAR-VARIABLE",	SC_FILE_EF_LINEAR_VARIABLE	},
	{ "LINEAR-VARIABLE-TLV",SC_FILE_EF_LINEAR_VARIABLE_TLV	},
	{ "CYCLIC",		SC_FILE_EF_CYCLIC	},
	{ "CYCLIC-TLV",		SC_FILE_EF_CYCLIC_TLV	},
	{ NULL, 0 }
};
static struct map		pkcs15DfNames[] = {
	{ "PRKDF",		SC_PKCS15_PRKDF		},
	{ "PUKDF",		SC_PKCS15_PUKDF		},
	{ "PUKDF-TRUSTED",	SC_PKCS15_PUKDF_TRUSTED	},
	{ "SKDF",		SC_PKCS15_SKDF		},
	{ "CDF",		SC_PKCS15_CDF		},
	{ "CDF-TRUSTED",	SC_PKCS15_CDF_TRUSTED	},
	{ "CDF-USEFUL",		SC_PKCS15_CDF_USEFUL	},
	{ "DODF",		SC_PKCS15_DODF		},
	{ "AODF",		SC_PKCS15_AODF		},
	{ NULL, 0 }
};
static struct map		pinTypeNames[] = {
	{ "BCD",		SC_PKCS15_PIN_TYPE_BCD	},
	{ "ascii-numeric",	SC_PKCS15_PIN_TYPE_ASCII_NUMERIC	},
	{ "utf8",		SC_PKCS15_PIN_TYPE_UTF8	},
	{ "half-nibble-bcd",	SC_PKCS15_PIN_TYPE_HALFNIBBLE_BCD	},
	{ "iso9564-1",		SC_PKCS15_PIN_TYPE_ISO9564_1	},
	{ NULL, 0 }
};
static struct map		pinIdNames[] = {
	{ "pin",		SC_PKCS15INIT_USER_PIN	},
	{ "puk",		SC_PKCS15INIT_USER_PUK	},
	{ "user-pin",		SC_PKCS15INIT_USER_PIN	},
	{ "user-puk",		SC_PKCS15INIT_USER_PUK	},
	{ "sopin",		SC_PKCS15INIT_SO_PIN	},
	{ "sopuk",		SC_PKCS15INIT_SO_PUK	},
	{ "so-pin",		SC_PKCS15INIT_SO_PIN	},
	{ "so-puk",		SC_PKCS15INIT_SO_PUK	},
	{ NULL, 0 }
};
static struct map		pinFlagNames[] = {
	{ "case-sensitive",		SC_PKCS15_PIN_FLAG_CASE_SENSITIVE		},
	{ "local",			SC_PKCS15_PIN_FLAG_LOCAL			},
	{ "change-disabled",		SC_PKCS15_PIN_FLAG_CHANGE_DISABLED		},
	{ "unblock-disabled",		SC_PKCS15_PIN_FLAG_UNBLOCK_DISABLED		},
	{ "initialized",		SC_PKCS15_PIN_FLAG_INITIALIZED			},
	{ "needs-padding",		SC_PKCS15_PIN_FLAG_NEEDS_PADDING		},
	{ "unblockingPin",		SC_PKCS15_PIN_FLAG_UNBLOCKING_PIN		},
	{ "soPin",			SC_PKCS15_PIN_FLAG_SO_PIN			},
	{ "disable-allowed",		SC_PKCS15_PIN_FLAG_DISABLE_ALLOW		},
	{ "integrity-protected",	SC_PKCS15_PIN_FLAG_INTEGRITY_PROTECTED		},
	{ "confidentiality-protected",	SC_PKCS15_PIN_FLAG_CONFIDENTIALITY_PROTECTED	},
	{ "exchangeRefData",		SC_PKCS15_PIN_FLAG_EXCHANGE_REF_DATA		},
	{ NULL, 0 }
};
static struct map		idStyleNames[] = {
	{ "native",		SC_PKCS15INIT_ID_STYLE_NATIVE },
	{ "mozilla",		SC_PKCS15INIT_ID_STYLE_MOZILLA },
	{ "rfc2459",		SC_PKCS15INIT_ID_STYLE_RFC2459 },
	{ NULL, 0 }
};
static struct map              mdStyleNames[] = {
	{ "none",               SC_PKCS15INIT_MD_STYLE_NONE },
	{ "gemalto",            SC_PKCS15INIT_MD_STYLE_GEMALTO },
	{ NULL, 0 }
};
static struct {
	const char *		name;
	struct map *		addr;
} mapNames[] = {
	{ "file ACL",		aclNames	},
	{ "file operation",	fileOpNames	},
	{ "file type",		fileTypeNames	},
	{ "file structure",	fileStructureNames},
	{ "PKCS#15 file name",	pkcs15DfNames	},
	{ "pin encoding",	pinTypeNames	},
	{ "pin name",		pinIdNames	},
	{ "pin flag",		pinFlagNames	},
	{ NULL, NULL }
};

static int		process_conf(struct sc_profile *, scconf_context *);
static int		process_block(struct state *, struct block *,
				const char *, scconf_block *);
static void		init_state(struct state *, struct state *);
static int		get_authid(struct state *, const char *,
				unsigned int *, unsigned int *);
static int		get_uint(struct state *, const char *, unsigned int *);
static int		get_bool(struct state *, const char *, unsigned int *);
static int		get_uint_eval(struct state *, int, char **,
				unsigned int *);
static int		map_str2int(struct state *, const char *,
				unsigned int *, struct map *);
static int		setstr(char **strp, const char *value);
static void		parse_error(struct state *, const char *, ...);

static struct file_info *	sc_profile_instantiate_file(sc_profile_t *,
				struct file_info *, struct file_info *,
				unsigned int);
static struct file_info *	sc_profile_find_file(struct sc_profile *,
				const sc_path_t *, const char *);
static struct file_info *	sc_profile_find_file_by_path(
				struct sc_profile *,
				const sc_path_t *);

static struct pin_info *	new_pin(struct sc_profile *, int);
static struct file_info *	new_file(struct state *, const char *,
				unsigned int);
static struct file_info *	add_file(sc_profile_t *, const char *,
				sc_file_t *, struct file_info *);
static void		free_file_list(struct file_info **);
static void		append_file(sc_profile_t *, struct file_info *);
static struct auth_info *	new_key(struct sc_profile *,
				unsigned int, unsigned int);
static void		set_pin_defaults(struct sc_profile *,
				struct pin_info *);
static int		new_macro(sc_profile_t *, const char *, scconf_list *);
static sc_macro_t *	find_macro(sc_profile_t *, const char *);
static int		is_macro_character(char c);

static sc_file_t *
init_file(unsigned int type)
{
	struct sc_file	*file;
	unsigned int	op;

	file = sc_file_new();
	for (op = 0; op < SC_MAX_AC_OPS; op++) {
		sc_file_add_acl_entry(file, op, SC_AC_NONE, 0);
	}
	file->type = type;
	file->status = SC_FILE_STATUS_ACTIVATED;
	if (file->type != SC_FILE_TYPE_DF && file->type != SC_FILE_TYPE_BSO)
		file->ef_structure = SC_FILE_EF_TRANSPARENT;
	return file;
}

/*
 * Initialize profile
 */
struct sc_profile *
sc_profile_new(void)
{
	struct sc_pkcs15_card *p15card;
	struct sc_profile *pro;

	pro = calloc(1, sizeof(*pro));
	if (pro == NULL)
		return NULL;
	pro->p15_spec = p15card = sc_pkcs15_card_new();

	pro->pkcs15.do_last_update = 1;

	if (p15card) {
		p15card->tokeninfo->label = strdup("OpenSC Card");
		p15card->tokeninfo->manufacturer_id = strdup("OpenSC Project");
		p15card->tokeninfo->serial_number = strdup("0000");
		p15card->tokeninfo->flags = SC_PKCS15_TOKEN_EID_COMPLIANT;
		p15card->tokeninfo->version = 0;

		/* Set up EF(TokenInfo) and EF(ODF) */
		p15card->file_tokeninfo = init_file(SC_FILE_TYPE_WORKING_EF);
		p15card->file_odf = init_file(SC_FILE_TYPE_WORKING_EF);
		p15card->file_unusedspace = init_file(SC_FILE_TYPE_WORKING_EF);
	}

	/* Assume card does RSA natively */
	pro->rsa_access_flags = DEF_PRKEY_RSA_ACCESS;
	pro->pin_encoding = SC_PKCS15_PIN_TYPE_ASCII_NUMERIC;
	pro->pin_minlen = 4;
	pro->pin_maxlen = 8;
	pro->id_style = SC_PKCS15INIT_ID_STYLE_NATIVE;

	return pro;
}

int
sc_profile_load(struct sc_profile *profile, const char *filename)
{
	struct sc_context *ctx = profile->card->ctx;
	scconf_context	*conf;
	const char *profile_dir = NULL;
	char path[PATH_MAX];
	int res = 0, i;
#ifdef _WIN32
	char temp_path[PATH_MAX];
	size_t temp_len;
#endif

	LOG_FUNC_CALLED(ctx);
	for (i = 0; ctx->conf_blocks[i]; i++) {
		profile_dir = scconf_get_str(ctx->conf_blocks[i], "profile_dir", NULL);
		if (profile_dir)
			break;
	}

	if (!profile_dir) {
#ifdef _WIN32
		temp_len = PATH_MAX - 1;
		res = sc_ctx_win32_get_config_value(NULL, "ProfileDir", "Software\\" OPENSC_VS_FF_COMPANY_NAME "\\OpenSC" OPENSC_ARCH_SUFFIX,
				temp_path, &temp_len);
		if (res)
			LOG_FUNC_RETURN(ctx, res);
		temp_path[temp_len] = '\0';
		profile_dir = temp_path;
#else
		profile_dir = SC_PKCS15_PROFILE_DIRECTORY;
#endif
	}
	sc_log(ctx, "Using profile directory '%s'.", profile_dir);

#ifdef _WIN32
	snprintf(path, sizeof(path), "%s\\%s.%s", profile_dir, filename, SC_PKCS15_PROFILE_SUFFIX);
#else /* _WIN32 */
	snprintf(path, sizeof(path), "%s/%s.%s", profile_dir, filename, SC_PKCS15_PROFILE_SUFFIX);
#endif /* _WIN32 */

	sc_log(ctx, "Trying profile file %s", path);

	conf = scconf_new(path);
	res = scconf_parse(conf);

	sc_log(ctx, "profile %s loaded ok", path);

	if (res < 0) {
		scconf_free(conf);
		LOG_FUNC_RETURN(ctx, SC_ERROR_FILE_NOT_FOUND);
	}

	if (res == 0) {
		scconf_free(conf);
		LOG_FUNC_RETURN(ctx, SC_ERROR_SYNTAX_ERROR);
	}

	res = process_conf(profile, conf);
	scconf_free(conf);
	LOG_FUNC_RETURN(ctx, res);
}


int
sc_profile_finish(struct sc_profile *profile, const struct sc_app_info *app_info)
{
	struct sc_context *ctx = profile->card->ctx;
	struct file_info *fi;
	struct pin_info	*pi;
	char		reason[64];

	LOG_FUNC_CALLED(ctx);
	profile->mf_info = sc_profile_find_file(profile, NULL, "MF");
	if (!profile->mf_info)
		LOG_TEST_RET(ctx, SC_ERROR_INCONSISTENT_PROFILE, "Profile doesn't define a MF");

	if (app_info && app_info->aid.len)   {
		struct sc_path path;

		sc_log(ctx, "finish profile with '%s' application profile", app_info->label);
		memset(&path, 0, sizeof(struct sc_path));
		path.type = SC_PATH_TYPE_DF_NAME;
		path.aid = app_info->aid;

		sc_log(ctx, "Look for file by path '%s'", sc_print_path(&path));
		profile->df_info = sc_profile_find_file_by_path(profile, &path);
		sc_log(ctx, "returned DF info %p", profile->df_info);
		if (profile->df_info && profile->df_info->profile_extension)   {
			sc_log(ctx, "application profile extension '%s'", profile->df_info->profile_extension);
			if (sc_profile_load(profile, profile->df_info->profile_extension))
				LOG_TEST_RET(ctx, SC_ERROR_INCONSISTENT_PROFILE, "Cannot load application profile extension");
		}
	}

	profile->df_info = sc_profile_find_file(profile, NULL, "PKCS15-AppDF");
	if (!profile->df_info)
		LOG_TEST_RET(ctx, SC_ERROR_INCONSISTENT_PROFILE, "Profile doesn't define a PKCS15-AppDF");

	profile->p15_spec->file_app = profile->df_info->file;
	profile->df_info->dont_free = 1;

	for (pi = profile->pin_list; pi; pi = pi->next) {
		const char	*name;

		set_pin_defaults(profile, pi);
		if (!(name = pi->file_name))
			continue;
		if (!(fi = sc_profile_find_file(profile, NULL, name))) {
			snprintf(reason, sizeof(reason), "unknown PIN file \"%s\"\n", name);
			goto whine;
		}

		pi->file = fi;
	}
	LOG_FUNC_RETURN(ctx, SC_SUCCESS);

whine:
	sc_log(ctx, "%s", reason);
	LOG_FUNC_RETURN(ctx, SC_ERROR_INCONSISTENT_PROFILE);
}

void
sc_profile_free(struct sc_profile *profile)
{
	struct auth_info *ai;
	struct pin_info *pi;
	sc_macro_t	*mi;
	sc_template_t	*ti;

	if (profile->name)
		free(profile->name);
	if (profile->driver)
		free(profile->driver);

	free_file_list(&profile->ef_list);

	while ((ai = profile->auth_list) != NULL) {
		profile->auth_list = ai->next;
		free(ai);
	}

	while ((ti = profile->template_list) != NULL) {
		profile->template_list = ti->next;
		if (ti->data)
			sc_profile_free(ti->data);
		if (ti->name)
			free(ti->name);
		free(ti);
	}

	while ((mi = profile->macro_list) != NULL) {
		profile->macro_list = mi->next;
		if (mi->name)
			free(mi->name);
		free(mi);
	}

	while ((pi = profile->pin_list) != NULL) {
		profile->pin_list = pi->next;
		if (pi->file_name)
			free(pi->file_name);
		free(pi);
	}

	for (int i = 0; profile->options[i]; i++) {
		free(profile->options[i]);
	}

	if (profile->p15_spec)
		sc_pkcs15_card_free(profile->p15_spec);

	if (profile->dll)
		sc_dlclose(profile->dll);

	free(profile);
}

void
sc_profile_get_pin_info(struct sc_profile *profile,
		int id, struct sc_pkcs15_auth_info *info)
{
	struct pin_info	*pi;

	pi = new_pin(profile, id);
	if (pi == NULL)
		return;

	pi->pin.max_tries = pi->pin.tries_left;
	*info = pi->pin;
}

int
sc_profile_get_pin_retries(sc_profile_t *profile, int id)
{
	struct pin_info	*pi;

	pi = new_pin(profile, id);
	if (pi == NULL)
		return SC_ERROR_OUT_OF_MEMORY;
	return pi->pin.tries_left;
}

int
sc_profile_get_pin_id(struct sc_profile *profile,
		unsigned int reference, int *id)
{
	struct pin_info	*pi;

	for (pi = profile->pin_list; pi; pi = pi->next) {
		if (pi->pin.auth_type != SC_PKCS15_PIN_AUTH_TYPE_PIN)
			continue;
		if (pi->pin.attrs.pin.reference == (int)reference) {
			*id = pi->id;
			return 0;
		}

	}
	return SC_ERROR_OBJECT_NOT_FOUND;
}

int
sc_profile_get_file_in(sc_profile_t *profile,
		const sc_path_t *path, const char *name, sc_file_t **ret)
{
	struct file_info *fi;

	if ((fi = sc_profile_find_file(profile, path, name)) == NULL)
		return SC_ERROR_FILE_NOT_FOUND;
	sc_file_dup(ret, fi->file);
	if (*ret == NULL)
		return SC_ERROR_OUT_OF_MEMORY;
	return 0;
}

int
sc_profile_get_file(struct sc_profile *profile,
		const char *name, sc_file_t **ret)
{
	struct file_info *fi;

	if ((fi = sc_profile_find_file(profile, NULL, name)) == NULL)
		return SC_ERROR_FILE_NOT_FOUND;
	sc_file_dup(ret, fi->file);
	if (*ret == NULL)
		return SC_ERROR_OUT_OF_MEMORY;
	return 0;
}

int
sc_profile_get_file_instance(struct sc_profile *profile, const char *name,
		int index, sc_file_t **ret)
{
	struct sc_context *ctx = profile->card->ctx;
	struct file_info *fi;
	struct sc_file *file;
	int r;

	LOG_FUNC_CALLED(ctx);
	sc_log(ctx, "try to get '%s' file instance", name);

	if ((fi = sc_profile_find_file(profile, NULL, name)) == NULL)
		LOG_FUNC_RETURN(ctx, SC_ERROR_FILE_NOT_FOUND);
	sc_file_dup(&file, fi->file);
	sc_log(ctx, "ident '%s'; parent '%s'", fi->ident, fi->parent ? fi->parent->ident : "(null)");
	if (file == NULL)
		LOG_FUNC_RETURN(ctx, SC_ERROR_OUT_OF_MEMORY);
	sc_log(ctx, "file (type:%X, path:'%s')", file->type, sc_print_path(&file->path));

	file->id += index;
        if(file->type == SC_FILE_TYPE_BSO) {
		r = sc_profile_add_file(profile, name, file);
		if (r < 0)
			sc_file_free(file);
		LOG_TEST_RET(ctx, r, "Profile error: cannot add BSO file");
	}
	else if (file->path.len)   {
		file->path.value[file->path.len - 2] = (file->id >> 8) & 0xFF;
		file->path.value[file->path.len - 1] = file->id & 0xFF;

		r = sc_profile_add_file(profile, name, file);
		if (r < 0)
			sc_file_free(file);
		LOG_TEST_RET(ctx, r, "Profile error: cannot add file");
	}

	if (ret)
		*ret = file;
	else
		sc_file_free(file);

	LOG_FUNC_RETURN(ctx, SC_SUCCESS);
}

int
sc_profile_get_path(struct sc_profile *profile,
		const char *name, sc_path_t *ret)
{
	struct file_info *fi;

	if ((fi = sc_profile_find_file(profile, NULL, name)) == NULL)
		return SC_ERROR_FILE_NOT_FOUND;
	*ret = fi->file->path;
	return 0;
}

int
sc_profile_get_file_by_path(struct sc_profile *profile,
		const sc_path_t *path, sc_file_t **ret)
{
	struct sc_context *ctx = profile->card->ctx;
	struct file_info *fi;

	LOG_FUNC_CALLED(ctx);
	if ((fi = sc_profile_find_file_by_path(profile, path)) == NULL)
		LOG_FUNC_RETURN(ctx, SC_ERROR_FILE_NOT_FOUND);
	sc_file_dup(ret, fi->file);
	LOG_FUNC_RETURN(ctx, *ret ? SC_SUCCESS : SC_ERROR_OUT_OF_MEMORY);
}

int
sc_profile_add_file(sc_profile_t *profile, const char *name, sc_file_t *file)
{
	struct sc_context *ctx = profile->card->ctx;
	sc_path_t	path = file->path;
	struct file_info	*parent;

	LOG_FUNC_CALLED(ctx);
	if (!path.len)   {
		parent = profile->df_info;
	} else {
		path.len -= 2;
		parent = sc_profile_find_file_by_path(profile, &path);
	}
	if (!parent)
		LOG_FUNC_RETURN(ctx, SC_ERROR_FILE_NOT_FOUND);
	sc_log(ctx, "Parent path:%s", sc_print_path(&parent->file->path));

	sc_file_dup(&file, file);
	if (file == NULL)
		LOG_FUNC_RETURN(ctx, SC_ERROR_OUT_OF_MEMORY);

	add_file(profile, name, file, parent);
	LOG_FUNC_RETURN(ctx, SC_SUCCESS);
}

/*
 * Instantiate template
 */
int
sc_profile_instantiate_template(sc_profile_t *profile,
		const char *template_name, const sc_path_t *base_path,
		const char *file_name, const sc_pkcs15_id_t *id,
		sc_file_t **ret)
{
	struct sc_context *ctx = profile->card->ctx;
	struct sc_profile	*tmpl;
	struct sc_template	*info;
	unsigned int	idx;
	struct file_info *fi, *base_file, *match = NULL;

#ifdef DEBUG_PROFILE
	printf("Instantiate %s in template %s\n", file_name, template_name);
	sc_profile_find_file_by_path(profile, base_path);
#endif
	for (info = profile->template_list; info; info = info->next)
		if (!strcmp(info->name, template_name))
			break;
	if (info == NULL)   {
		sc_log(ctx, "Template %s not found", template_name);
		return SC_ERROR_TEMPLATE_NOT_FOUND;
	}

	tmpl = info->data;
	idx = id->value[id->len-1];
	for (fi = profile->ef_list; fi; fi = fi->next) {
		if (fi->base_template == tmpl
		 && fi->inst_index == idx
		 && sc_compare_path(&fi->inst_path, base_path)
		 && !strcmp(fi->ident, file_name)) {
			sc_file_dup(ret, fi->file);
			if (*ret == NULL)
				return SC_ERROR_OUT_OF_MEMORY;
			return 0;
		}
	}

	sc_log(ctx, "Instantiating template %s at %s", template_name, sc_print_path(base_path));

	base_file = sc_profile_find_file_by_path(profile, base_path);
	if (base_file == NULL) {
		sc_log(ctx, "Directory %s not defined in profile", sc_print_path(base_path));
		return SC_ERROR_OBJECT_NOT_FOUND;
	}

	/* This loop relies on the fact that new files are always
	 * appended to the list, after the parent files they refer to
	 */
	assert(base_file->instance);
	for (fi = tmpl->ef_list; fi; fi = fi->next) {
		struct file_info	*parent, *instance;
		unsigned int	skew = 0;

		fi->instance = NULL;
		if ((parent = fi->parent) == NULL) {
			parent = base_file;
			skew = idx;
		}
		parent = parent->instance;

		instance = sc_profile_instantiate_file(profile, fi, parent, skew);
		if (instance == NULL)
			return SC_ERROR_OUT_OF_MEMORY;
		instance->base_template = tmpl;
		instance->inst_index = idx;
		instance->inst_path = *base_path;

		if (!strcmp(instance->ident, file_name))
			match = instance;
	}

	if (match == NULL) {
		sc_log(ctx, "No file named \"%s\" in template \"%s\"",
				file_name, template_name);
		return SC_ERROR_OBJECT_NOT_FOUND;
	}
	sc_file_dup(ret, match->file);
	if (*ret == NULL)
		return SC_ERROR_OUT_OF_MEMORY;
#ifdef DEBUG_PROFILE
	printf("Template instantiated\n");
#endif
	return 0;
}

static struct file_info *
sc_profile_instantiate_file(sc_profile_t *profile, struct file_info *ft,
		struct file_info *parent, unsigned int skew)
{
	struct sc_context *ctx = profile->card->ctx;
	struct file_info *fi;

	fi = calloc(1, sizeof(*fi));
	if (fi == NULL)
		return NULL;
	fi->instance = fi;
	fi->parent = parent;
	fi->ident = strdup(ft->ident);
	if (fi->ident == NULL) {
		free(fi);
		return NULL;
	}
	sc_file_dup(&fi->file, ft->file);
	if (fi->file == NULL) {
		free(fi->ident);
		free(fi);
		return NULL;
	}
	fi->file->path = parent->file->path;
	fi->file->id += skew;

	if (fi->file->type == SC_FILE_TYPE_INTERNAL_EF
			|| fi->file->type == SC_FILE_TYPE_WORKING_EF
			|| (fi->file->type == SC_FILE_TYPE_DF && fi->file->id))
		sc_append_file_id(&fi->file->path, fi->file->id);

	append_file(profile, fi);

	ft->instance = fi;

	sc_log(ctx, "Instantiated %s at %s", ft->ident, sc_print_path(&fi->file->path));
	sc_log(ctx, "  parent=%s@%s", parent->ident, sc_print_path(&parent->file->path));

	return fi;
}

int
sc_profile_get_pin_id_by_reference(struct sc_profile *profile,
		unsigned auth_method, int reference,
		struct sc_pkcs15_auth_info *auth_info)
{
	struct pin_info *pinfo;

	for (pinfo = profile->pin_list; pinfo; pinfo = pinfo->next)  {
		if (auth_method == SC_AC_SYMBOLIC)   {
			if (pinfo->id != reference)
				continue;
		}
		else   {
			if (pinfo->pin.auth_type != SC_PKCS15_PIN_AUTH_TYPE_PIN)
				continue;
			if (pinfo->pin.auth_method != auth_method)
				continue;
			if (pinfo->pin.attrs.pin.reference != reference)
				continue;
		}

		if (auth_info)
			*auth_info = pinfo->pin;
		return pinfo->id;
	}

	return -1;
}

/*
 * Configuration file parser
 */
static void
init_state(struct state *cur_state, struct state *new_state)
{
	memset(new_state, 0, sizeof(*new_state));
	new_state->filename = cur_state->filename;
	new_state->profile = cur_state->profile;
	new_state->frame = cur_state;
}

static int
do_card_driver(struct state *cur, int argc, char **argv)
{
	free(cur->profile->driver);
	cur->profile->driver = strdup(argv[0]);
	return 0;
}

static int
do_maxpinlength(struct state *cur, int argc, char **argv)
{
	return get_uint(cur, argv[0], &cur->profile->pin_maxlen);
}

static int
do_minpinlength(struct state *cur, int argc, char **argv)
{
	return get_uint(cur, argv[0], &cur->profile->pin_minlen);
}

static int
do_default_pin_type(struct state *cur, int argc, char **argv)
{
	return map_str2int(cur, argv[0],
		       	&cur->profile->pin_encoding, pinTypeNames);
}

static int
do_pin_pad_char(struct state *cur, int argc, char **argv)
{
	return get_uint(cur, argv[0], &cur->profile->pin_pad_char);
}

static int
do_pin_domains(struct state *cur, int argc, char **argv)
{
	return get_bool(cur, argv[0], &cur->profile->pin_domains);
}

static int
do_card_label(struct state *cur, int argc, char **argv)
{
	struct sc_pkcs15_card	*p15card = cur->profile->p15_spec;

	return setstr(&p15card->tokeninfo->label, argv[0]);
}

static int
do_card_manufacturer(struct state *cur, int argc, char **argv)
{
	struct sc_pkcs15_card	*p15card = cur->profile->p15_spec;

	return setstr(&p15card->tokeninfo->manufacturer_id, argv[0]);
}

/*
 * Command related to the pkcs15 we generate
 */
static int
do_direct_certificates(struct state *cur, int argc, char **argv)
{
	return get_bool(cur, argv[0], &cur->profile->pkcs15.direct_certificates);
}

static int
do_encode_df_length(struct state *cur, int argc, char **argv)
{
	return get_bool(cur, argv[0], &cur->profile->pkcs15.encode_df_length);
}

static int
do_encode_update_field(struct state *cur, int argc, char **argv)
{
	return get_bool(cur, argv[0], &cur->profile->pkcs15.do_last_update);
}

static int
do_pkcs15_id_style(struct state *cur, int argc, char **argv)
{
	return map_str2int(cur, argv[0], &cur->profile->id_style, idStyleNames);
}

static int
do_minidriver_support_style(struct state *cur, int argc, char **argv)
{
	return map_str2int(cur, argv[0], &cur->profile->md_style, mdStyleNames);
}

/*
 * Process an option block
 */
static int
process_option(struct state *cur, struct block *info,
		const char *name, scconf_block *blk)
{
	sc_profile_t	*profile = cur->profile;
	int		match = 0, i;

	for (i = 0; profile->options[i]; i++)
		match |= !strcmp(profile->options[i], name);
	if (!match && strcmp("default", name))
		return 0;
	return process_block(cur, info, name, blk);
}

/*
 * Process a key block
 */
static int
process_key(struct state *cur, struct block *info,
		const char *name, scconf_block *blk)
{
	unsigned int	type, id;
	struct state	state;

	if (get_authid(cur, name, &type, &id))
		return 1;

	init_state(cur, &state);
	state.key = new_key(cur->profile, type, id);
	return process_block(&state, info, name, blk);
}

static struct auth_info *
new_key(struct sc_profile *profile, unsigned int type, unsigned int ref)
{
	struct auth_info *ai, **aip;

	for (aip = &profile->auth_list; (ai = *aip); aip = &ai->next) {
		if (ai->type == type && ai->ref == ref)
			return ai;
	}

	ai = calloc(1, sizeof(*ai));
	if (ai == NULL)
		return NULL;
	ai->type = type;
	ai->ref = ref;
	*aip = ai;
	return ai;
}

static int
do_key_value(struct state *cur, int argc, char **argv)
{
	struct auth_info *ai = cur->key;
	const char	*key = argv[0];
	size_t		key_len;
	unsigned char	keybuf[32];

	if (key[0] == '=') {
		++key;
		key_len = strlen(key);
		if (key_len > sizeof(keybuf)) {
		    parse_error(cur, "Key value too long (%zu > %zu)\n", key_len, sizeof(keybuf));
		    return 1;
		}
		memcpy(keybuf, key, key_len);
	} else {
		key_len = sizeof(keybuf);
		if (sc_hex_to_bin(key, keybuf, &key_len)) {
			parse_error(cur, "Error parsing PIN/key \"%s\"\n", key);
			return 1;
		}
	}

	memcpy(ai->key, keybuf, key_len);
	ai->key_len = key_len;
	return 0;
}

/*
 * This function is called when the parser finds a block with an unknown
 * name in the filesystem block. This will create a new filesystem
 * object as the child of the current object.
 */
static int
process_df(struct state *cur, struct block *info,
		const char *name, scconf_block *blk)
{
	struct state	state;

	init_state(cur, &state);
	if (name == NULL) {
		parse_error(cur, "No name given for DF object.");
		return 1;
	}
	if (!(state.file = new_file(cur, name, SC_FILE_TYPE_DF)))
		return 1;
	return process_block(&state, info, name, blk);
}

static int
process_ef(struct state *cur, struct block *info,
		const char *name, scconf_block *blk)
{
	struct state	state;

	init_state(cur, &state);
	if (name == NULL) {
		parse_error(cur, "No name given for EF object.");
		return 1;
	}
	if (!(state.file = new_file(cur, name, SC_FILE_TYPE_WORKING_EF)))
		return 1;
	return process_block(&state, info, name, blk);
}


static int
process_bso(struct state *cur, struct block *info,
		const char *name, scconf_block *blk)
{
	struct state	state;

	init_state(cur, &state);
	if (name == NULL) {
		parse_error(cur, "No name given for BSO object.");
		return 1;
	}
	if (!(state.file = new_file(cur, name, SC_FILE_TYPE_BSO)))
		return 1;
	return process_block(&state, info, name, blk);
}

/*
 * In the template the difference between any two file-ids
 * should be greater then TEMPLATE_FILEID_MIN_DIFF.
 */
static int
template_sanity_check(struct state *cur, struct sc_profile *templ)
{
	struct file_info *fi, *ffi;

	for (fi = templ->ef_list; fi; fi = fi->next) {
		struct sc_path fi_path =  fi->file->path;
		int fi_id;

		if (fi->file->type == SC_FILE_TYPE_BSO)
			continue;

		if (fi_path.len < 2) {
			parse_error(cur, "Template insane: file-path length should not be less than 2 bytes");
			return 1;
		}

		fi_id = fi_path.value[fi_path.len - 2] * 0x100
				+ fi_path.value[fi_path.len - 1];

		for (ffi = templ->ef_list; ffi; ffi = ffi->next) {
			struct sc_path ffi_path =  ffi->file->path;
			int dlt, ffi_id;

			if (ffi->file->type == SC_FILE_TYPE_BSO)
				continue;

			if (ffi_path.len < 2) {
				parse_error(cur, "Template insane: file-path length should not be less than 2 bytes");
				return 1;
			}

			ffi_id = ffi_path.value[ffi_path.len - 2] * 0x100
					+ ffi_path.value[ffi_path.len - 1];

			dlt = fi_id > ffi_id ? fi_id - ffi_id : ffi_id - fi_id;
			if (strcmp(ffi->ident, fi->ident))   {
				if (dlt >= TEMPLATE_FILEID_MIN_DIFF)
					continue;

				parse_error(cur, "Template insane: file-ids should be substantially different");
				return 1;
			}
		}
	}

	return SC_SUCCESS;
}


static int
process_tmpl(struct state *cur, struct block *info,
		const char *name, scconf_block *blk)
{
	struct state	state;
	sc_template_t	*tinfo;
	sc_profile_t	*templ;
	int r;

#ifdef DEBUG_PROFILE
	printf("Process template:%s; block:%s\n", name, info->name);
#endif
	if (name == NULL) {
		parse_error(cur, "No name given for template.");
		return 1;
	}

	templ = calloc(1, sizeof(*templ));
	if (templ == NULL) {
		parse_error(cur, "memory allocation failed");
		return 1;
	}

	tinfo = calloc(1, sizeof(*tinfo));
	if (tinfo == NULL) {
		parse_error(cur, "memory allocation failed");
		free(templ);
		return 1;
	}
	tinfo->name = strdup(name);
	tinfo->data = templ;

	tinfo->next = cur->profile->template_list;
	cur->profile->template_list = tinfo;

	init_state(cur, &state);
	state.profile = tinfo->data;
	state.file = NULL;

	r = process_block(&state, info, name, blk);
	if (!r)
		r = template_sanity_check(cur, templ);

#ifdef DEBUG_PROFILE
	printf("Template %s processed; returns %i\n", name, r);
#endif
	return r;
}

/*
 * Append new file at the end of the ef_list.
 * This is crucial; the profile instantiation code relies on it
 */
static void append_file(sc_profile_t *profile, struct file_info *nfile)
{
	struct file_info	**list, *fi;

	list = &profile->ef_list;
	while ((fi = *list) != NULL)
		list = &fi->next;
	*list = nfile;
}

/*
 * Add a new file to the profile.
 * This function is called by sc_profile_add_file.
 */
static struct file_info *
add_file(sc_profile_t *profile, const char *name,
		sc_file_t *file, struct file_info *parent)
{
	struct file_info	*info;

	info = calloc(1, sizeof(*info));
	if (info == NULL)
		return NULL;
	info->instance = info;
	info->ident = strdup(name);

	info->parent = parent;
	info->file = file;

	append_file(profile, info);
	return info;
}

/*
 * Free file_info list
 */
static void
free_file_list(struct file_info **list)
{
	struct file_info	*fi;

	while ((fi = *list) != NULL) {
		*list = fi->next;

		if (fi->dont_free == 0)
			sc_file_free(fi->file);
		free(fi->profile_extension);
		free(fi->ident);
		free(fi);
	}
}

/*
 * Create a new file info object.
 * This function is called by the profile parser.
 */
static struct file_info *
new_file(struct state *cur, const char *name, unsigned int type)
{
	sc_profile_t	*profile = cur->profile;
	struct file_info	*info;
	sc_file_t	*file;
	unsigned int	df_type = 0, dont_free = 0;
	int	free_file = 0;

	if ((info = sc_profile_find_file(profile, NULL, name)) != NULL)
		return info;

	/* Special cases for those EFs handled separately
	 * by the PKCS15 logic */
	if (strncasecmp(name, "PKCS15-", 7)) {
		file = init_file(type);
		free_file = 1;
	} else if (!strcasecmp(name+7, "TokenInfo")) {
		if (!profile->p15_spec) {
			parse_error(cur, "no pkcs15 spec in profile");
			return NULL;
		}
		file = profile->p15_spec->file_tokeninfo;
		dont_free = 1;
	} else if (!strcasecmp(name+7, "ODF")) {
		if (!profile->p15_spec) {
			parse_error(cur, "no pkcs15 spec in profile");
			return NULL;
		}
		file = profile->p15_spec->file_odf;
		dont_free = 1;
	} else if (!strcasecmp(name+7, "UnusedSpace")) {
		if (!profile->p15_spec) {
			parse_error(cur, "no pkcs15 spec in profile");
			return NULL;
		}
		file = profile->p15_spec->file_unusedspace;
		dont_free = 1;
	} else if (!strcasecmp(name+7, "AppDF")) {
		file = init_file(SC_FILE_TYPE_DF);
		free_file = 1;
	} else {
		if (map_str2int(cur, name+7, &df_type, pkcs15DfNames)
				|| df_type >= SC_PKCS15_DF_TYPE_COUNT)
			return NULL;

		file = init_file(SC_FILE_TYPE_WORKING_EF);
		profile->df[df_type] = file;
		free_file = 1;
	}
	assert(file);
	if (file->type != type) {
		parse_error(cur, "inconsistent file type (should be %s)",
			file->type == SC_FILE_TYPE_DF
				? "DF" : file->type == SC_FILE_TYPE_BSO
					? "BS0" : "EF");
		if (free_file)
			sc_file_free(file);
		return NULL;
	}

	info = add_file(profile, name, file, cur->file);
	if (info == NULL) {
		parse_error(cur, "memory allocation failed");
		return NULL;
	}
	info->dont_free = dont_free;
	return info;
}

static int
do_file_type(struct state *cur, int argc, char **argv)
{
	unsigned int	type;

	if (!cur->file) {
		parse_error(cur, "Invalid state\n");
		return 1;
	}

	if (map_str2int(cur, argv[0], &type, fileTypeNames))
		return 1;
	cur->file->file->type = type;
	return 0;
}

static int
do_file_path(struct state *cur, int argc, char **argv)
{
	struct sc_file	*file = NULL;
	struct sc_path	*path = NULL;

	if (!cur->file) {
		parse_error(cur, "Invalid state\n");
		return 1;
	}
	file = cur->file->file;
	path = &file->path;

	/* sc_format_path doesn't return an error indication
	 * when it's unable to parse the path */
	sc_format_path(argv[0], path);
	if (!path->len || (path->len & 1)) {
		parse_error(cur, "Invalid path length\n");
		return 1;
	}
	file->id = (path->value[path->len-2] << 8) | path->value[path->len-1];
	return 0;
}

static int
do_fileid(struct state *cur, int argc, char **argv)
{
	struct file_info *fi;
	struct sc_file	*df, *file = NULL;
	struct sc_path	temp, *path = NULL;

	if (!cur->file) {
		parse_error(cur, "Invalid state\n");
		return 1;
	}
	file = cur->file->file;
	path = &file->path;

	/* sc_format_path doesn't return an error indication
	 * when it's unable to parse the path */
	sc_format_path(argv[0], &temp);
	if (temp.len != 2) {
		parse_error(cur, "Invalid file ID length\n");
		return 1;
	}

	/* Get the DF, if any */
	if ((fi = cur->file->parent) && (df = fi->file)) {
		if (!df->path.len && !df->path.aid.len) {
			parse_error(cur, "No path/fileid set for parent DF\n");
			return 1;
		}
		if (df->path.len + 2 > sizeof(df->path.value)) {
			parse_error(cur, "File path too long\n");
			return 1;
		}
		*path = df->path;
	}
	if (path->len + 2 > sizeof(path->value)) {
		parse_error(cur, "File path too long\n");
		return 1;
	}
	memcpy(path->value + path->len, temp.value, 2);
	path->len += 2;

	file->id = (temp.value[0] << 8) | temp.value[1];
	return 0;
}

static int
do_structure(struct state *cur, int argc, char **argv)
{
	unsigned int	ef_structure;

	if (!cur->file) {
		parse_error(cur, "Invalid state\n");
		return 1;
	}

	if (map_str2int(cur, argv[0], &ef_structure, fileStructureNames))
		return 1;
	cur->file->file->ef_structure = ef_structure;
	return 0;
}

static int
do_size(struct state *cur, int argc, char **argv)
{
	unsigned int	size;

	if (!cur->file) {
		parse_error(cur, "Invalid state\n");
		return 1;
	}

	if (get_uint_eval(cur, argc, argv, &size))
		return 1;
	cur->file->file->size = size;
	return 0;
}

static int
do_reclength(struct state *cur, int argc, char **argv)
{
	unsigned int	reclength;

	if (!cur->file) {
		parse_error(cur, "Invalid state\n");
		return 1;
	}

	if (get_uint(cur, argv[0], &reclength))
		return 1;
	cur->file->file->record_length = reclength;
	return 0;
}

static int
do_content(struct state *cur, int argc, char **argv)
{
	struct sc_file *file = NULL;
	size_t len = (strlen(argv[0]) + 1) / 2;
	int rv = 0;

	if (!cur->file) {
		parse_error(cur, "Invalid state\n");
		return 1;
	}
	file = cur->file->file;

	free(file->encoded_content);

	file->encoded_content = malloc(len);
	if (!file->encoded_content)
		return 1;
	rv = sc_hex_to_bin(argv[0], file->encoded_content, &len);
	file->encoded_content_len = len;
	return rv;
}

static int
do_prop_attr(struct state *cur, int argc, char **argv)
{
	struct sc_file *file = NULL;
	size_t len = (strlen(argv[0]) + 1) / 2;
	int rv = 0;

	if (!cur->file) {
		parse_error(cur, "Invalid state\n");
		return 1;
	}
	file = cur->file->file;

	free(file->prop_attr);
	file->prop_attr = malloc(len);
	if (!file->prop_attr)
		return 1;
	rv = sc_hex_to_bin(argv[0], file->prop_attr, &len);
	file->prop_attr_len = len;
	return rv;
}

static int
do_aid(struct state *cur, int argc, char **argv)
{
	struct sc_file	*file = NULL;
	const char	*name = argv[0];
	size_t len;
	int		res = 0;

	if (!cur->file) {
		parse_error(cur, "Invalid state\n");
		return 1;
	}
	file = cur->file->file;

	if (*name == '=') {
		len = strlen(++name);
		if (len > sizeof(file->name)) {
			parse_error(cur, "AID \"%s\" too long\n", name);
			return 1;
		}
		memcpy(file->name, name, len);
		file->namelen = len;
	}
	else {
		file->namelen = sizeof(file->name);
		res = sc_hex_to_bin(name, file->name, &file->namelen);
	}
	return res;
}

static int
do_exclusive_aid(struct state *cur, int argc, char **argv)
{
	struct sc_file	*file = NULL;
	const char	*name = argv[0];
	size_t len;
	int		res = 0;

	if (!cur->file) {
		parse_error(cur, "Invalid state\n");
		return 1;
	}
	file = cur->file->file;

#ifdef DEBUG_PROFILE
	printf("do_exclusive_aid(): exclusive-aid '%s'\n", name);
	printf("do_exclusive_aid(): current file '%s' (path:%s)\n", cur->file->ident, sc_print_path(&file->path));
#endif
	sc_format_path(name, &file->path);
	if (file->path.len > SC_MAX_AID_SIZE)   {
		parse_error(cur, "Path length is too big\n");
		return 1;
	}

	memcpy(file->path.aid.value, file->path.value, file->path.len);
	file->path.aid.len = file->path.len;

	file->path.len = 0;
	file->path.type = SC_PATH_TYPE_DF_NAME;

#ifdef DEBUG_PROFILE
	printf("do_exclusive_aid(): '%s' exclusive-aid path %s\n", cur->file->ident, sc_print_path(&file->path));
#endif
	if (*name == '=') {
		len = strlen(++name);
		if (len > sizeof(file->name)) {
			parse_error(cur, "AID \"%s\" too long\n", name);
			return 1;
		}
		memcpy(file->name, name, len);
		file->namelen = len;
	}
	else {
		file->namelen = sizeof(file->name);
		res = sc_hex_to_bin(name, file->name, &file->namelen);
	}
	return res;
}

static int
do_profile_extension(struct state *cur, int argc, char **argv)
{
	if (!cur->file) {
		parse_error(cur, "Invalid state\n");
		return 1;
	}
	return setstr(&cur->file->profile_extension, argv[0]);
}

/*
 * Parse ACL list.
 * The way we do this is we first split things like CHV1
 * into a method (SC_AC_CHV) and a reference (1).
 * When we're finished parsing the profile, the fake references
 * are replaced by the real references given in KEY or PIN
 * commands
 */
static int
do_acl(struct state *cur, int argc, char **argv)
{
	struct sc_file	*file = NULL;
	char		oper[64], *what = NULL;
	memset(oper, 0, sizeof(oper));

	if (!cur->file)
		goto bad;
	file = cur->file->file;

	while (argc--) {
		unsigned int	op, method, id;

		if (strlen(*argv) >= sizeof(oper))
			goto bad;
		strlcpy(oper, *argv++, sizeof(oper));

		if ((what = strchr(oper, '=')) == NULL)
			goto bad;
		*what++ = '\0';

		if (*what == '$') {
			method = SC_AC_SYMBOLIC;
			if (map_str2int(cur, what+1, &id, pinIdNames))
				return 1;
		}
		else if (get_authid(cur, what, &method, &id))
			goto bad;


		if (!strcmp(oper, "*")) {
			for (op = 0; op < SC_MAX_AC_OPS; op++) {
				sc_file_clear_acl_entries(file, op);
				sc_file_add_acl_entry(file, op, method, id);
			}
		} else {
			const sc_acl_entry_t *acl;

			if (map_str2int(cur, oper, &op, fileOpNames))
				goto bad;
			if (!(acl = sc_file_get_acl_entry(file, op)))
				goto bad;
			if (acl->method == SC_AC_NEVER
			 || acl->method == SC_AC_NONE
			 || acl->method == SC_AC_UNKNOWN)
				sc_file_clear_acl_entries(file, op);

			sc_file_add_acl_entry(file, op, method, id);
		}
	}
	return 0;

bad:	parse_error(cur,
		"Invalid ACL \"%s%s%s\"\n",
		oper, what? "=" : "", what? what : "");
	return 1;
}

static int
process_pin(struct state *cur, struct block *info,
		const char *name, scconf_block *blk)
{
	struct state	state;
	unsigned int	id;

	if (map_str2int(cur, name, &id, pinIdNames))
		return 1;

	init_state(cur, &state);
	state.pin = new_pin(cur->profile, (int)id);

	return process_block(&state, info, name, blk);
}

static struct pin_info *
new_pin(struct sc_profile *profile, int id)
{
	struct pin_info	*pi, **tail;

	for (tail = &profile->pin_list; (pi = *tail); tail = &pi->next) {
		if (pi->id == id)
			return pi;
	}

	/* Create pin info object. Most values are
	 * set to their defaults in set_pin_defaults later
	 * We can't do this here because these pin info objects
	 * are usually created before we've read the card specific
	 * profile
	 */
	pi = calloc(1, sizeof(*pi));
	if (pi == NULL)
		return NULL;
	pi->id = id;
	pi->pin.auth_type = SC_PKCS15_PIN_AUTH_TYPE_PIN;
	pi->pin.auth_method = SC_AC_CHV;
	pi->pin.attrs.pin.type = (unsigned int)-1;
	pi->pin.attrs.pin.flags = 0x32;
	pi->pin.attrs.pin.max_length = 0;
	pi->pin.attrs.pin.min_length = 0;
	pi->pin.attrs.pin.stored_length = 0;
	pi->pin.attrs.pin.pad_char = 0xA5;
	pi->pin.attrs.pin.reference = -1;
	pi->pin.tries_left = 3;

	*tail = pi;
	return pi;
}

static void set_pin_defaults(struct sc_profile *profile, struct pin_info *pi)
{
	struct sc_pkcs15_auth_info *info = &pi->pin;
	struct sc_pkcs15_pin_attributes *pin_attrs = &info->attrs.pin;

	info->auth_type = SC_PKCS15_PIN_AUTH_TYPE_PIN;

	if (pin_attrs->type == (unsigned int) -1)
		pin_attrs->type = profile->pin_encoding;
	if (pin_attrs->max_length == 0)
		pin_attrs->max_length = profile->pin_maxlen;
	if (pin_attrs->min_length == 0)
		pin_attrs->min_length = profile->pin_minlen;
	if (pin_attrs->stored_length == 0) {
		pin_attrs->stored_length = profile->pin_maxlen;
		/* BCD encoded PIN takes half the space */
		if (pin_attrs->type == SC_PKCS15_PIN_TYPE_BCD)
			pin_attrs->stored_length = (pin_attrs->stored_length + 1) / 2;
	}
	if (pin_attrs->pad_char == 0xA5)
		pin_attrs->pad_char = profile->pin_pad_char;
}

static int
do_pin_file(struct state *cur, int argc, char **argv)
{
	free(cur->pin->file_name);
	cur->pin->file_name = strdup(argv[0]);
	return 0;
}

static int
do_pin_offset(struct state *cur, int argc, char **argv)
{
	return get_uint(cur, argv[0], &cur->pin->file_offset);
}

static int
do_pin_attempts(struct state *cur, int argc, char **argv)
{
	struct pin_info	*pi = cur->pin;
	unsigned int	count;

	if (get_uint(cur, argv[0], &count))
		return 1;
	pi->pin.tries_left = count;
	return 0;
}

static int
do_pin_maxunlocks(struct state *cur, int argc, char **argv)
{
	struct pin_info	*pi = cur->pin;
	unsigned int	count;

	if (get_uint(cur, argv[0], &count))
		return 1;
	pi->pin.max_unlocks = count;
	return 0;
}

static int
do_pin_type(struct state *cur, int argc, char **argv)
{
	unsigned int	type;

	if (map_str2int(cur, argv[0], &type, pinTypeNames))
		return 1;
	if (cur->pin->pin.auth_type != SC_PKCS15_PIN_AUTH_TYPE_PIN)
		return 1;
	cur->pin->pin.attrs.pin.type = type;
	return 0;
}

static int
do_pin_reference(struct state *cur, int argc, char **argv)
{
	unsigned int	reference;

	if (get_uint(cur, argv[0], &reference))
		return 1;
	if (cur->pin->pin.auth_type != SC_PKCS15_PIN_AUTH_TYPE_PIN)
		return 1;
	cur->pin->pin.attrs.pin.reference = reference;
	return 0;
}

static int
do_pin_authid(struct state *cur, int argc, char **argv)
{
	sc_pkcs15_format_id(argv[0], &cur->pin->pin.auth_id);
	return 0;
}

static int
do_pin_minlength(struct state *cur, int argc, char **argv)
{
	unsigned int	len;

	if (get_uint(cur, argv[0], &len))
		return 1;
	if (cur->pin->pin.auth_type != SC_PKCS15_PIN_AUTH_TYPE_PIN)
		return 1;
	cur->pin->pin.attrs.pin.min_length = len;
	return 0;
}

static int
do_pin_maxlength(struct state *cur, int argc, char **argv)
{
	unsigned int	len;

	if (get_uint(cur, argv[0], &len))
		return 1;
	if (cur->pin->pin.auth_type != SC_PKCS15_PIN_AUTH_TYPE_PIN)
		return 1;
	cur->pin->pin.attrs.pin.max_length = len;
	return 0;
}

static int
do_pin_storedlength(struct state *cur, int argc, char **argv)
{
	unsigned int	len;

	if (get_uint(cur, argv[0], &len))
		return 1;
	if (cur->pin->pin.auth_type != SC_PKCS15_PIN_AUTH_TYPE_PIN)
		return 1;
	cur->pin->pin.attrs.pin.stored_length = len;
	return 0;
}

static int
do_pin_flags(struct state *cur, int argc, char **argv)
{
	unsigned int flags = 0;
	int		i, r;

	if (cur->pin->pin.auth_type != SC_PKCS15_PIN_AUTH_TYPE_PIN)
		return -1;

	cur->pin->pin.attrs.pin.flags = 0;
	for (i = 0; i < argc; i++) {
		if ((r = map_str2int(cur, argv[i], &flags, pinFlagNames)) < 0)
			return r;
		cur->pin->pin.attrs.pin.flags |= flags;
	}

	return 0;
}

static int
process_macros(struct state *cur, struct block *info,
		const char *dummy, scconf_block *blk)
{
	scconf_item	*item;
	const char	*name;
	int		 r;

	for (item = blk->items; item; item = item->next) {
		char *s = item->key;
		name = item->key;
		if (item->type != SCCONF_ITEM_TYPE_VALUE || !name)
			continue;

		/* make sure the macro name consist only of allowed characters.
		 * This is not guaranteed by the tokenizer */
		while (is_macro_character(*s)) {
			s++;
		}
		if (*s != '\0') {
#ifdef DEBUG_PROFILE
			printf("Invalid macro name %s\n", name);
#endif
			return SC_ERROR_SYNTAX_ERROR;
		}
#ifdef DEBUG_PROFILE
		printf("Defining %s\n", name);
#endif
		r = new_macro(cur->profile, name, item->value.list);
		if (r != SC_SUCCESS)
			return r;
	}

	return SC_SUCCESS;
}

static int
new_macro(sc_profile_t *profile, const char *name, scconf_list *value)
{
	sc_macro_t	*mac;

	if (!profile || !name || !value)
		return SC_ERROR_INVALID_ARGUMENTS;

	if ((mac = find_macro(profile, name)) == NULL) {
		mac = calloc(1, sizeof(*mac));
		if (mac == NULL)
			return SC_ERROR_OUT_OF_MEMORY;
		mac->name = strdup(name);
		mac->next = profile->macro_list;
		profile->macro_list = mac;
	}

	mac->value = value;
	return SC_SUCCESS;
}

static sc_macro_t *
find_macro(sc_profile_t *profile, const char *name)
{
	sc_macro_t	*mac;

	for (mac = profile->macro_list; mac; mac = mac->next) {
		if (!strcmp(mac->name, name))
			return mac;
	}
	return NULL;
}

/*
 * Key section
 */
static struct command	key_commands[] = {
 { "value",		1,	1,	do_key_value	},
 { NULL, 0, 0, NULL }
};

/*
 * Cardinfo section
 */
static struct command	ci_commands[] = {
 { "driver",		1,	1,	do_card_driver	},
 { "max-pin-length",	1,	1,	do_maxpinlength	},
 { "min-pin-length",	1,	1,	do_minpinlength	},
 { "pin-encoding",	1,	1,	do_default_pin_type },
 { "pin-pad-char",	1,	1,	do_pin_pad_char },
 { "pin-domains",	1,	1,	do_pin_domains	},
 { "label",		1,	1,	do_card_label	},
 { "manufacturer",	1,	1,	do_card_manufacturer},

 { NULL, 0, 0, NULL }
};

static struct block	ci_blocks[] = {
 { "key",		process_key,	key_commands,	NULL	},

 { NULL, NULL, NULL, NULL }
};

/*
 * Filesystem section
 */
static struct command	fs_commands[] = {
 { "type",		1,	1,	do_file_type	},
 { "path",		1,	1,	do_file_path	},
 { "file-id",		1,	1,	do_fileid	},
 { "structure",		1,	1,	do_structure	},
 { "size",		1,	-1,	do_size		},
 { "record-length",	1,	1,	do_reclength	},
 { "AID",		1,	1,	do_aid		},
 { "ACL",		1,	-1,	do_acl		},
/* AID dependent sub-profile */
 { "profile-extension",	1,	1,	do_profile_extension	},
/* AID of the DFs without file-id */
 { "exclusive-aid",	1,	1,	do_exclusive_aid	},
 { "content",		1,	1,	do_content	},
 { "prop-attr",		1,	1,	do_prop_attr	},

 { NULL, 0, 0, NULL }
};

static struct block	fs_blocks[] = {
 { "DF",		process_df,	fs_commands,	fs_blocks },
 { "EF",		process_ef,	fs_commands,	fs_blocks },
 { "BSO",		process_bso,	fs_commands,	fs_blocks },
 { "template",		process_tmpl,	fs_commands,	fs_blocks },

 { NULL, NULL, NULL, NULL }
};

/*
 * Pin section
 */
static struct command	pi_commands[] = {
 { "file",		1,	1,	do_pin_file		},
 { "offset",		1,	1,	do_pin_offset		},
 { "attempts",		1,	2,	do_pin_attempts		},
 { "encoding",		1,	1,	do_pin_type		},
 { "reference",		1,	1,	do_pin_reference	},
 { "auth-id",		1,	1,	do_pin_authid		},
 { "max-length",	1,	1,	do_pin_maxlength	},
 { "min-length",	1,	1,	do_pin_minlength	},
 { "stored-length",	1,	1,	do_pin_storedlength	},
 { "max-unlocks",	1,	1,	do_pin_maxunlocks	},
 { "flags",		1,	-1,	do_pin_flags		},
 { NULL, 0, 0, NULL }
};

/*
 * pkcs15 dialect section
 */
static struct command	p15_commands[] = {
 { "direct-certificates",	1,	1,	do_direct_certificates },
 { "encode-df-length",		1,	1,	do_encode_df_length },
 { "do-last-update",		1,	1,	do_encode_update_field },
 { "pkcs15-id-style",		1,	1,	do_pkcs15_id_style },
 { "minidriver-support-style",	1,	1,	do_minidriver_support_style },
 { NULL, 0, 0, NULL }
};

static struct block	root_blocks[] = {
 { "filesystem",	process_block,	NULL,		fs_blocks },
 { "cardinfo",		process_block,	ci_commands,	ci_blocks },
 { "pin",		process_pin,	pi_commands,	NULL	},
 { "option",		process_option,	NULL,		root_blocks },
 { "macros",		process_macros,	NULL,		NULL	},
 { "pkcs15",		process_block,	p15_commands,	NULL	},

 { NULL, NULL, NULL, NULL }
};

static struct block	root_ops = {
   "root",		process_block,	NULL,		root_blocks
};

static int
is_macro_character(char c)
{
	if (isalnum(c) || c == '-' || c == '_')
		return 1;
	return 0;
}

static int
get_inner_word(const char *str, char word[WORD_SIZE])
{
	const char *inner = NULL;
	size_t len = 0;

	inner = str;

	while (is_macro_character(*inner)) {
		inner++;
		len++;
	}
	if (len >= WORD_SIZE)
		return 1;
	memcpy(word, str, len);
	word[len] = '\0';
	return 0;
}

/*
 * Checks for a reference loop for macro named start_name in macro definitions.
 * Function returns 1 if a reference loop is detected, 0 otherwise.
 */
static int
check_macro_reference_loop(const char *start_name, sc_macro_t *macro, sc_profile_t *profile, int depth)
{
	scconf_list *value;
	const char *name = NULL;
	sc_macro_t	*m;
	char word[WORD_SIZE];

	if (!start_name || !macro || !profile || depth == 16)
		return 1;

	/* For some reason, the macro value is a list where we need to check for references */
	for (value = macro->value; value != NULL; value = value->next) {
		/* Find name in macro value */
		char *macro_value = value->data;
		if (!(name = strchr(macro_value, '$')))
			continue;
		/* Extract the macro name from the string */
		if (get_inner_word(name + 1, word))
			return 1;
		/* Find whether name corresponds to some other macro */
		if (!(m = find_macro(profile, word)))
			continue;
		/* Check for loop */
		if (!strcmp(m->name, start_name))
			return 1;
		/* Reference loop was found to the original macro name */
		if (check_macro_reference_loop(start_name, m, profile, depth + 1) == 1) {
			return 1;
		}
	}
	return 0;
}

static int
build_argv(struct state *cur, const char *cmdname,
		scconf_list *list, char **argv, unsigned int max)
{
	unsigned int	argc;
	const char	*str;
	sc_macro_t	*macro;
	int		r;

	for (argc = 0; list; list = list->next) {
		if (argc >= max) {
			parse_error(cur, "%s: too many arguments", cmdname);
			return SC_ERROR_INVALID_ARGUMENTS;
		}

		str = list->data;
		if (str[0] != '$') {
			/* When str contains macro inside, macro reference loop needs to be checked */
			const char *macro_name = NULL;
			if ((macro_name = strchr(str, '$'))) {
				/* Macro does not have to start at the first position */
				char word[WORD_SIZE];
				if (get_inner_word(macro_name + 1, word) != 0) {
					return SC_ERROR_SYNTAX_ERROR;
				}
				if ((macro = find_macro(cur->profile, word))
				    && check_macro_reference_loop(macro->name, macro, cur->profile, 0)) {
					return SC_ERROR_SYNTAX_ERROR;
				}
			}

			argv[argc++] = list->data;
			continue;
		}

		/* Expand macro reference */
		if (!(macro = find_macro(cur->profile, str + 1))) {
			parse_error(cur, "%s: unknown macro \"%s\"",
					cmdname, str);
			return SC_ERROR_SYNTAX_ERROR;
		}

		if (list == macro->value) {
			return SC_ERROR_SYNTAX_ERROR;
		}
		if (check_macro_reference_loop(macro->name, macro, cur->profile, 0)) {
			return SC_ERROR_SYNTAX_ERROR;
		}
#ifdef DEBUG_PROFILE
		{
			scconf_list *list;

			printf("Expanding macro %s:", macro->name);
			for (list = macro->value; list; list = list->next)
				printf(" %s", list->data);
			printf("\n");
		}
#endif
		r = build_argv(cur, cmdname, macro->value,
				argv + argc, max - argc);
		if (r < 0)
			return r;

		argc += r;
	}

	return argc;
}

static int
process_command(struct state *cur, struct command *cmd_info, scconf_list *list)
{
	const char	*cmd = cmd_info->name;
	char		*argv[32];
	int		argc, max = 32;

	if (cmd_info->max_args >= 0 && max > cmd_info->max_args)
		max = cmd_info->max_args;

	if ((argc = build_argv(cur, cmd, list, argv, max)) < 0)
		return argc;

	if (argc < cmd_info->min_args) {
		parse_error(cur, "%s: not enough arguments\n", cmd);
		return 1;
	}
	return cmd_info->func(cur, argc, argv);
}

static struct block *
find_block_handler(struct block *bp, const char *name)
{
	if (bp == NULL)
		return NULL;
	for (; bp->name; bp++) {
		if (!strcasecmp(bp->name, name))
			return bp;
	}
	return NULL;
}

static struct command *
find_cmd_handler(struct command *cp, const char *name)
{
	if (cp == NULL)
		return NULL;
	for (; cp->name; cp++) {
		if (!strcasecmp(cp->name, name))
			return cp;
	}
	return NULL;
}

static int
process_block(struct state *cur, struct block *info,
		const char *name, scconf_block *blk)
{
	scconf_item	*item;
	struct command	*cp;
	struct block	*bp;
	const char	*cmd, *ident;
	int		res = 0;

	for (item = blk->items; res == 0 && item; item = item->next) {
		cmd = item->key;
		if (item->type == SCCONF_ITEM_TYPE_COMMENT)
			continue;
		if (!cmd) {
			parse_error(cur, "Command can not be processed.");
			return SC_ERROR_SYNTAX_ERROR;
		}
		if (item->type == SCCONF_ITEM_TYPE_BLOCK) {
			scconf_list *nlist;

			ident = NULL;
			if ((nlist = item->value.block->name) != NULL) {
				if (nlist->next) {
					parse_error(cur, "Too many name components in block name.");
					return SC_ERROR_SYNTAX_ERROR;
				}
				ident = nlist->data;
			}
#ifdef DEBUG_PROFILE
			printf("Processing %s %s\n", cmd, ident? ident : "");
#endif
			if ((bp = find_block_handler(info->blk_info, cmd))) {
				res = bp->handler(cur, bp, ident, item->value.block);
				continue;
			}
		}
		else if (item->type == SCCONF_ITEM_TYPE_VALUE) {
#ifdef DEBUG_PROFILE
			printf("Processing %s\n", cmd);
#endif
			if ((cp = find_cmd_handler(info->cmd_info, cmd))) {
				res = process_command(cur, cp, item->value.list);
				continue;
			}
		}
		parse_error(cur, "Command \"%s\" not understood in this context.", cmd);
		return SC_ERROR_SYNTAX_ERROR;
	}

	if (res > 0)
		res = SC_ERROR_SYNTAX_ERROR;
	return res;
}

static int
process_conf(struct sc_profile *profile, scconf_context *conf)
{
	struct state	state;

	memset(&state, 0, sizeof(state));
	state.filename = conf->filename;
	state.profile = profile;
	return process_block(&state, &root_ops, "root", conf->root);
}

static struct file_info *
sc_profile_find_file(struct sc_profile *pro,
		const sc_path_t *path, const char *name)
{
	struct file_info	*fi;
	size_t				len;
	const u8			*value;

	value = path ? path->value : (const u8*) "";
	len = path ? path->len : 0;
	for (fi = pro->ef_list; fi; fi = fi->next) {
		sc_path_t *fpath = &fi->file->path;

		if (!strcasecmp(fi->ident, name) && fpath->len >= len && !memcmp(fpath->value, value, len))
			return fi;
	}
	return NULL;
}


static struct file_info *
sc_profile_find_file_by_path(struct sc_profile *pro, const sc_path_t *path)
{
	struct file_info *fi, *out = NULL;
	struct sc_path *fp_path, *fpp_path;

#ifdef DEBUG_PROFILE
	struct sc_context *ctx = pro->card->ctx;

	sc_log(ctx, "profile's EF list:");
	for (fi = pro->ef_list; fi; fi = fi->next)   {
		sc_log(ctx, "'%s' (path:%s)",  fi->ident, sc_print_path(&fi->file->path));
		sc_log(ctx, "fi parent %p", fi->parent);
		if (fi->parent && fi->parent->file)
			sc_log(ctx, "fi parent path %s", sc_print_path(&fi->parent->file->path));
	}
	sc_log(ctx, "find profile file by path:%s", sc_print_path(path));
#endif

	if (!path || (!path->len && !path->aid.len))
		return NULL;

	for (fi = pro->ef_list; fi; fi = fi->next) {
		fp_path = &fi->file->path;
		fpp_path = fi->parent ? &fi->parent->file->path : NULL;

		if (fp_path->len != path->len)
			continue;
		if (fp_path->len && memcmp(fp_path->value, path->value, path->len))
			continue;

		if (path->aid.len && fp_path->aid.len)   {
			if (memcmp(fp_path->aid.value, path->aid.value, path->aid.len))
				continue;
		}
		else if (path->aid.len && !fp_path->aid.len && fpp_path)   {
			if (fpp_path->type == SC_PATH_TYPE_DF_NAME && fpp_path->len)   {
				if (fpp_path->len != path->aid.len)
					continue;
				if (memcmp(fpp_path->value, path->aid.value, path->aid.len))
					continue;
			}
			else if (fpp_path->aid.len)   {
				if (fpp_path->aid.len != path->aid.len)
					continue;
				if (memcmp(fpp_path->aid.value, path->aid.value, path->aid.len))
					continue;
			}
		}

		out = fi;
	}

#ifdef DEBUG_PROFILE
	sc_log(ctx, "returns (%s)", out ? out->ident: "<null>");
#endif
	return out;
}

int
sc_profile_get_parent(struct sc_profile *profile,
		const char *name, sc_file_t **ret)
{
	struct file_info *fi = NULL;

	if ((fi = sc_profile_find_file(profile, NULL, name)) == NULL)
		return SC_ERROR_FILE_NOT_FOUND;

	if (!fi->parent)
		return SC_ERROR_FILE_NOT_FOUND;

	sc_file_dup(ret, fi->parent->file);
	if (*ret == NULL)
		return SC_ERROR_OUT_OF_MEMORY;
	return 0;
}

/*
 * Split up KEY0 or CHV1 into SC_AC_XXX and a number
 */
static int
get_authid(struct state *cur, const char *value,
		unsigned int *type, unsigned int *num)
{
	char	temp[16];
	size_t	n;

	if (isdigit((unsigned char) *value)) {
		*num = 0;
		return get_uint(cur, value, type);
	}

	if (strlen(value) >= sizeof(temp))
		return 1;

	n = strcspn(value, "0123456789x");
	strlcpy(temp, value, (sizeof(temp) > n) ? n + 1 : sizeof(temp));

	if (map_str2int(cur, temp, type, aclNames))
		return 1;
	if (value[n])
		return get_uint(cur, value + n, num);
	*num = 0;
	return 0;
}

static int
get_uint(struct state *cur, const char *value, unsigned int *vp)
{
	char	*ep;
	unsigned long tmp;

	if (strstr(value, "0x") == value)
		tmp = strtoul(value + 2, &ep, 16);
	else if (strstr(value, "x") == value)
		tmp = strtoul(value + 1, &ep, 16);
	else
		tmp = strtoul(value, &ep, 0);
	if (*ep != '\0') {
		parse_error(cur, "invalid integer argument \"%s\"\n", value);
		return 1;
	}
	if (tmp > INT_MAX) {
		parse_error(cur, "the number \"%s\" is too large\n", value);
		return 1;
	}
	*vp = (int)tmp;
	return 0;
}

static int
get_bool(struct state *cur, const char *value, unsigned int *vp)
{
	if (!strcasecmp(value, "on")
	 || !strcasecmp(value, "yes")
	 || !strcasecmp(value, "true")) {
		*vp = 1;
	} else
	if (!strcasecmp(value, "off")
	 || !strcasecmp(value, "no")
	 || !strcasecmp(value, "false")) {
		*vp = 0;
	} else {
		parse_error(cur, "invalid boolean argument \"%s\"\n", value);
		return 1;
	}
	return 0;
}

static int
map_str2int(struct state *cur, const char *value,
		unsigned int *vp, struct map *map)
{
	unsigned int	n;
	const char	*what;

	if (isdigit((unsigned char) *value))
		return get_uint(cur, value, vp);
	for (n = 0; map[n].name; n++) {
		if (!strcasecmp(value, map[n].name)) {
			*vp = map[n].val;
			return 0;
		}
	}

	/* Try to print a meaningful error message */
	what = "argument";
	for (n = 0; mapNames[n].name; n++) {
		if (mapNames[n].addr == map) {
			what = mapNames[n].name;
			break;
		}
	}

	parse_error(cur, "invalid %s \"%s\"\n", what, value);
	return SC_ERROR_SYNTAX_ERROR;
}

static int
setstr(char **strp, const char *value)
{
	if (*strp)
		free(*strp);
	*strp = strdup(value);
	return 0;
}

/*
 * Evaluate numeric expressions
 */
#include <setjmp.h>

struct num_exp_ctx {
	struct state *	state;
	jmp_buf		error;

	int		j;
	char		word[WORD_SIZE];

	char *		unget;
	char *		str;
	int		argc;
	char **		argv;
};

static void	expr_eval(struct num_exp_ctx *, unsigned int *, unsigned int, int);

static void
expr_fail(struct num_exp_ctx *ctx)
{
	longjmp(ctx->error, 1);
}

static void
expr_put(struct num_exp_ctx *ctx, int c)
{
	if (ctx->j >= (int)sizeof(ctx->word))
		expr_fail(ctx);
	ctx->word[ctx->j++] = (char)c;
}

static char *
__expr_get(struct num_exp_ctx *ctx, int eof_okay)
{
	char	*s;

	if ((s = ctx->unget) != NULL) {
		ctx->unget = NULL;
		return s;
	}

	ctx->j = 0;
	s = ctx->str;
	do {
		if (s == NULL || *s == '\0') {
			if (ctx->argc == 0) {
				if (eof_okay)
					return NULL;
				expr_fail(ctx);
			}
			ctx->str = s = *(ctx->argv++);
			ctx->argc--;
		}

		while (isspace((unsigned char)*s))
			s++;
	} while (*s == '\0');

	if (isdigit((unsigned char)*s)) {
		while (isdigit((unsigned char)*s))
			expr_put(ctx, *s++);
	}
	else if (*s == '$') {
		expr_put(ctx, *s++);
		while (is_macro_character(*s))
			expr_put(ctx, *s++);
	}
	else if (strchr("*/+-()|&", *s)) {
		expr_put(ctx, *s++);
	}
	else {
		expr_fail(ctx);
	}
	ctx->str = s;

	expr_put(ctx, '\0');
	return ctx->word;
}

static char *
expr_get(struct num_exp_ctx *ctx)
{
	return __expr_get(ctx, 0);
}

static void
expr_unget(struct num_exp_ctx *ctx, char *s)
{
	if (ctx->unget)
		expr_fail(ctx);
	ctx->unget = s;
}

static void
expr_expect(struct num_exp_ctx *ctx, int c)
{
	char	*tok;

	tok = expr_get(ctx);
	if (tok[0] != (char)c || tok[1])
		expr_fail(ctx);
}

#define MAX_BRACKETS 32
static void
expr_term(struct num_exp_ctx *ctx, unsigned int *vp, int opening_brackets)
{
	char	*tok;

	tok = expr_get(ctx);
	if (*tok == '(') {
		if (opening_brackets + 1 > MAX_BRACKETS) {
			parse_error(ctx->state, "Too many \"%s\" in expression", tok);
			expr_fail(ctx);
		}
		expr_eval(ctx, vp, 1, opening_brackets + 1);
		expr_expect(ctx, ')');
	}
	else if (isdigit((unsigned char)*tok)) {
		char	*ep;
		unsigned long tmp;

		tmp = strtoul(tok, &ep, 0);
		if (*ep)
			expr_fail(ctx);
		if (tmp > UINT_MAX)
			expr_fail(ctx);
		*vp = (unsigned int)tmp;
	}
	else if (*tok == '$') {
		sc_macro_t	*mac;
		char		*argv[32];
		int		argc;

		if (!(mac = find_macro(ctx->state->profile, tok + 1)))
			expr_fail(ctx);
		argc = build_argv(ctx->state, "<expr>", mac->value, argv, 32);
		if (argc < 0 || get_uint_eval(ctx->state, argc, argv, vp) < 0)
			expr_fail(ctx);
	}
	else {
		parse_error(ctx->state, "Unexpected token \"%s\" in expression", tok);
		expr_fail(ctx);
	}
}

static void
expr_eval(struct num_exp_ctx *ctx, unsigned int *vp, unsigned int pri, int opening_brackets)
{
	unsigned int	left, right, new_pri;
	char		*tok, op;

	expr_term(ctx, &left, opening_brackets);

	while (1) {
		tok = __expr_get(ctx, 1);
		if (tok == NULL)
			break;

		op = tok[0];

		new_pri = 0;
		switch (op) {
		case '*':
		case '/':
			new_pri++;
			/* fall through */
		case '+':
		case '-':
			new_pri++;
			/* fall through */
		case '&':
			new_pri++;
			/* fall through */
		case '|':
			new_pri++;
			/* fall through */
		case ')':
			break;
		default:
			expr_fail(ctx);
		}

		if (new_pri < pri) {
			expr_unget(ctx, tok);
			break;
		}
		pri = new_pri;

		expr_eval(ctx, &right, new_pri + 1, opening_brackets);
		switch (op) {
		case '*': left *= right; break;
		case '/':
			if (right == 0)
				expr_fail(ctx);
			left /= right; break;
		case '+': left += right; break;
		case '-': left -= right; break;
		case '&': left &= right; break;
		case '|': left |= right; break;
		default: expr_fail(ctx);
		}
	}

	*vp = left;
}

static int
get_uint_eval(struct state *cur, int argc, char **argv, unsigned int *vp)
{
	struct num_exp_ctx	ctx;

	memset(&ctx, 0, sizeof(ctx));
	ctx.state = cur;
	ctx.argc  = argc;
	ctx.argv  = argv;

	if (setjmp(ctx.error)) {
		parse_error(cur, "invalid numeric expression\n");
		return SC_ERROR_SYNTAX_ERROR;
	}

	expr_eval(&ctx, vp, 0, 0);
	if (ctx.str[0] || ctx.argc)
		expr_fail(&ctx);

	return 0;
}

static void
parse_error(struct state *cur, const char *fmt, ...)
{
	char	buffer[1024], *sp;
	va_list	ap;

	va_start(ap, fmt);
	vsnprintf(buffer, sizeof(buffer), fmt, ap);
	va_end(ap);

	if ((sp = strchr(buffer, '\n')) != NULL)
		*sp = '\0';

	if (cur->profile->card && cur->profile->card->ctx)
		sc_log(cur->profile->card->ctx, "%s: %s", cur->filename, buffer);
	else
		fprintf(stdout, "%s: %s\n", cur->filename, buffer);
}
