/*
 * opensc-tool.c: Tool for accessing smart cards with libopensc
 *
 * Copyright (C) 2001  Juha Yrjölä <juha.yrjola@iki.fi>
 *
 * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#include "config.h"

#include <stdio.h>
#include <stdlib.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <string.h>
#include <errno.h>
#include <ctype.h>
#include <sys/stat.h>

#include "libopensc/opensc.h"
#include "libopensc/cardctl.h"
#include "util.h"

/* type for associations of IDs to names */
typedef struct _id2str {
	unsigned int id;
	const char *str;
} id2str_t;

static const char *app_name = "opensc-tool";

static int	opt_wait = 0;
static char **	opt_apdus;
static char	*opt_reader;
static int	opt_apdu_count = 0;
static int	verbose = 0;

enum {
	OPT_SERIAL = 0x100,
	OPT_LIST_ALG,
	OPT_VERSION
};

static const struct option options[] = {
	{ "version",		0, NULL,	OPT_VERSION },
	{ "info",		0, NULL,		'i' },
	{ "atr",		0, NULL,		'a' },
	{ "serial",		0, NULL,	OPT_SERIAL  },
	{ "name",		0, NULL,		'n' },
	{ "get-conf-entry",	1, NULL,		'G' },
	{ "set-conf-entry",	1, NULL,		'S' },
	{ "list-readers",	0, NULL,		'l' },
	{ "list-drivers",	0, NULL,		'D' },
	{ "list-files",		0, NULL,		'f' },
	{ "send-apdu",		1, NULL,		's' },
	{ "reader",		1, NULL,		'r' },
	{ "card-driver",	1, NULL,		'c' },
	{ "list-algorithms",    0, NULL,	OPT_LIST_ALG },
	{ "wait",		0, NULL,		'w' },
	{ "verbose",		0, NULL,		'v' },
	{ NULL, 0, NULL, 0 }
};

static const char *option_help[] = {
	"Prints OpenSC package revision",
	"Prints information about OpenSC",
	"Prints the ATR bytes of the card",
	"Prints the card serial number",
	"Identify the card and print its name",
	"Get configuration key, format: section:name:key",
	"Set configuration key, format: section:name:key:value",
	"Lists readers",
	"Lists all installed card drivers",
	"Recursively lists files stored on card",
	"Sends an APDU in format AA:BB:CC:DD:EE:FF...",
	"Uses reader number <arg> [0]",
	"Forces the use of driver <arg> [auto-detect]",
	"Lists algorithms supported by card",
	"Wait for a card to be inserted",
	"Verbose operation. Use several times to enable debug output.",
};

static sc_context_t *ctx = NULL;
static sc_card_t *card = NULL;

static int opensc_info(void)
{
	printf (
		"%s %s ",
		PACKAGE_NAME,
		PACKAGE_VERSION
	);

#if defined(__VERSION__)
	printf (
		"[%s %s]\n",
#if defined(__GNUC__)
		"gcc ",
#else
		"unknown ",
#endif
		__VERSION__
	);
#elif defined(__SUNPRO_C)
	printf (
		"[Sun C %x.%x]\n",
#if __SUNPRO_C > 0x590
		(__SUNPRO_C >> 12), (__SUNPRO_C >> 4) & 0xFF
#else
		(__SUNPRO_C >>  8), (__SUNPRO_C >> 4) & 0xF
#endif
	);
#elif defined(_MSC_VER)
	printf ("[Microsoft %d]\n", _MSC_VER);
#else
	printf ("[Unknown compiler, please report]");
#endif
	printf ("Enabled features:%s\n", OPENSC_FEATURES);
	return 0;
}

static int opensc_get_conf_entry(const char *config)
{
	scconf_block *conf_block = NULL, **blocks;
	char *buffer = NULL;
	char *section = NULL;
	char *name = NULL;
	char *key = NULL;
	int r = 0;

	if (ctx->conf == NULL) {
		r = ENOENT;
		goto cleanup;
	}

	if ((buffer = strdup(config)) == NULL) {
		r = ENOMEM;
		goto cleanup;
	}

	section = buffer;
	name = strchr(section+1, ':');
	key = name == NULL ? NULL : strchr(name+1, ':');
	if (key == NULL) {
		r = EINVAL;
		goto cleanup;
	}
	*name = '\0';
	name++;
	*key = '\0';
	key++;

	blocks = scconf_find_blocks(ctx->conf, NULL, section, name);
	if (blocks[0])
		conf_block = blocks[0];
	free(blocks);
	if (conf_block != NULL) {
		const char *value = scconf_get_str(conf_block, key, NULL);

		if (value != NULL) {
			printf ("%s\n", value);
		}
	}

	r = 0;

cleanup:

	if (buffer != NULL)
		free(buffer);

	return r;
}

static int opensc_set_conf_entry(const char *config)
{
	scconf_block *conf_block = NULL, **blocks;
	char *buffer = NULL;
	char *section = NULL;
	char *name = NULL;
	char *key = NULL;
	char *value = NULL;
	int r = 0;

	if (ctx->conf == NULL) {
		r = ENOENT;
		goto cleanup;
	}

	if ((buffer = strdup(config)) == NULL) {
		r = ENOMEM;
		goto cleanup;
	}

	section = buffer;
	name = strchr(section+1, ':');
	key = name == NULL ? NULL : strchr(name+1, ':');
	value = key == NULL ? NULL : strchr(key+1, ':');
	if (value == NULL) {
		r = EINVAL;
		goto cleanup;
	}
	*name = '\0';
	name++;
	*key = '\0';
	key++;
	*value = '\0';
	value++;

	blocks = scconf_find_blocks(ctx->conf, NULL, section, name);
	if (blocks[0])
		conf_block = blocks[0];
	free(blocks);
	if (conf_block != NULL) {
		scconf_item *item;

		for (item = conf_block->items; item != NULL; item = item->next) {
			scconf_list *list;

			if ((item->type != SCCONF_ITEM_TYPE_VALUE)
			   || (strcmp(item->key, key) != 0))
				continue;
			list = item->value.list;
			scconf_list_destroy(list);
			list = NULL;
			scconf_list_add(&list, value);
			item->value.list = list;
			break;
		}
		if (item == NULL)
			scconf_put_str(conf_block, key, value);
	}

	/* Write */
	if ((r = scconf_write(ctx->conf, ctx->conf->filename)) != 0) {
		fprintf(stderr, "scconf_write(): %s\n", strerror(r));
		goto cleanup;
	}

	r = 0;

cleanup:

	if (buffer != NULL)
		free(buffer);

	return r;
}

static int list_readers(void)
{
	unsigned int i, rcount = sc_ctx_get_reader_count(ctx);

	if (rcount == 0) {
		printf("No smart card readers found.\n");
		return 0;
	}
	printf("# Detected readers (%s)\n", ctx->reader_driver->short_name);
	printf("Nr.  Card  Features  Name\n");
	for (i = 0; i < rcount; i++) {
		sc_reader_t *reader = sc_ctx_get_reader(ctx, i);
		int state = sc_detect_card_presence(reader);
		printf("%-5d%-6s%-10s%s\n", i, state & SC_READER_CARD_PRESENT ? "Yes":"No",
		      reader->capabilities & SC_READER_CAP_PIN_PAD ? "PIN pad":"",
		      reader->name);
		if (state & SC_READER_CARD_PRESENT && verbose) {
			struct sc_card *card;
			int r;
			char tmp[SC_MAX_ATR_SIZE*3];
			sc_bin_to_hex(reader->atr.value, reader->atr.len, tmp, sizeof(tmp) - 1, ':');

			if (state & SC_READER_CARD_EXCLUSIVE)
				printf("     %s [EXCLUSIVE]\n", tmp);
			else {
				if ((r = sc_connect_card(reader, &card)) != SC_SUCCESS) {
					fprintf(stderr, "     failed: %s\n", sc_strerror(r));
				} else {
					printf("     %s %s %s\n", tmp, card->name, state & SC_READER_CARD_INUSE ? "[IN USE]" : "");
					sc_disconnect_card(card);
				}
			}
		}
	}
	return 0;
}

static int list_drivers(void)
{
	int i;

	if (ctx->card_drivers[0] == NULL) {
		printf("No card drivers installed!\n");
		return 0;
	}
	printf("Configured card drivers:\n");
	for (i = 0; ctx->card_drivers[i] != NULL; i++) {
		printf("  %-16s %s\n", ctx->card_drivers[i]->short_name,
		      ctx->card_drivers[i]->name);
	}
	return 0;
}

static int print_file(sc_card_t *in_card, const sc_file_t *file,
	const sc_path_t *path, int depth)
{
	int r;
	const char *tmps;

	for (r = 0; r < depth; r++)
		printf("  ");
	printf("%s ", sc_print_path(path));
	if (file->namelen) {
		printf("[");
		util_print_binary(stdout, file->name, file->namelen);
		printf("] ");
	}
	switch (file->type) {
	case SC_FILE_TYPE_WORKING_EF:
		tmps = "wEF";
		break;
	case SC_FILE_TYPE_INTERNAL_EF:
		tmps = "iEF";
		break;
	case SC_FILE_TYPE_DF:
		tmps = "DF";
		break;
	default:
		tmps = "unknown";
		break;
	}
	printf("type: %s, ", tmps);
	if (file->type != SC_FILE_TYPE_DF) {
		const id2str_t ef_type_name[] = {
			{ SC_FILE_EF_TRANSPARENT,         "transparent"           },
			{ SC_FILE_EF_LINEAR_FIXED,        "linear-fixed"          },
			{ SC_FILE_EF_LINEAR_FIXED_TLV,    "linear-fixed (TLV)"    },
			{ SC_FILE_EF_LINEAR_VARIABLE,     "linear-variable"       },
			{ SC_FILE_EF_LINEAR_VARIABLE_TLV, "linear-variable (TLV)" },
			{ SC_FILE_EF_CYCLIC,              "cyclic"                },
			{ SC_FILE_EF_CYCLIC_TLV,          "cyclic (TLV)"          },
			{ 0, NULL }
		};
		const char *ef_type = "unknown";

		for (r = 0; ef_type_name[r].str != NULL; r++)
			if (file->ef_structure == ef_type_name[r].id)
				ef_type = ef_type_name[r].str;

		printf("ef structure: %s, ", ef_type);
	}
	printf("size: %lu\n", (unsigned long) file->size);
	for (r = 0; r < depth; r++)
		printf("  ");
	if (file->type == SC_FILE_TYPE_DF) {
		const id2str_t ac_ops_df[] = {
			{ SC_AC_OP_SELECT,       "select" },
			{ SC_AC_OP_LOCK,         "lock"   },
			{ SC_AC_OP_DELETE,       "delete" },
			{ SC_AC_OP_CREATE,       "create" },
			{ SC_AC_OP_REHABILITATE, "rehab"  },
			{ SC_AC_OP_INVALIDATE,   "inval"  },
			{ SC_AC_OP_LIST_FILES,   "list"   },
			{ 0, NULL }
		};

		for (r = 0; ac_ops_df[r].str != NULL; r++)
			printf("%s[%s] ", ac_ops_df[r].str,
					 util_acl_to_str(sc_file_get_acl_entry(file, ac_ops_df[r].id)));
	}
	else {
		const id2str_t ac_ops_ef[] = {
			{ SC_AC_OP_READ,         "read"   },
			{ SC_AC_OP_UPDATE,       "update" },
			{ SC_AC_OP_ERASE,        "erase"  },
			{ SC_AC_OP_WRITE,        "write"  },
			{ SC_AC_OP_REHABILITATE, "rehab"  },
			{ SC_AC_OP_INVALIDATE,   "inval"  },
			{ 0, NULL }
		};

		for (r = 0; ac_ops_ef[r].str != NULL; r++)
			printf("%s[%s] ", ac_ops_ef[r].str,
					util_acl_to_str(sc_file_get_acl_entry(file, ac_ops_ef[r].id)));
	}

	if (file->sec_attr_len) {
		printf("sec: ");
		/* Octets are as follows:
		*   DF: select, lock, delete, create, rehab, inval
		*   EF: read, update, write, erase, rehab, inval
		* 4 MSB's of the octet mean:
		*  0 = ALW, 1 = PIN1, 2 = PIN2, 4 = SYS,
		* 15 = NEV */
		util_hex_dump(stdout, file->sec_attr, file->sec_attr_len, ":");
	}
	if (file->prop_attr_len) {
		printf("\n");
		for (r = 0; r < depth; r++)
			printf("  ");
		printf("prop: ");
		util_hex_dump(stdout, file->prop_attr, file->prop_attr_len, ":");
	}
	printf("\n\n");

	if (file->type == SC_FILE_TYPE_DF)
		return 0;

	if (file->ef_structure == SC_FILE_EF_TRANSPARENT) {
		unsigned char *buf;

		if (!(buf = malloc(file->size))) {
			fprintf(stderr, "out of memory");
			return 1;
		}

		r = sc_read_binary(in_card, 0, buf, file->size, 0);
		if (r > 0)
			util_hex_dump_asc(stdout, buf, r, 0);
		free(buf);
	} else {
		unsigned char buf[256];
		int i;

		for (i=0; i < file->record_count; i++) {
			printf("Record %d\n", i);
			r = sc_read_record(in_card, i, buf, 256, 0);
			if (r > 0)
				util_hex_dump_asc(stdout, buf, r, 0);
		}
	}
	return 0;
}

static int enum_dir(sc_path_t path, int depth)
{
	sc_file_t *file;
	int r, file_type;
	u8 files[SC_MAX_APDU_BUFFER_SIZE];

	r = sc_select_file(card, &path, &file);
	if (r) {
		fprintf(stderr, "SELECT FILE failed: %s\n", sc_strerror(r));
		return 1;
	}
	print_file(card, file, &path, depth);
	file_type = file->type;
	sc_file_free(file);
	if (file_type == SC_FILE_TYPE_DF) {
		int i;

		r = sc_list_files(card, files, sizeof(files));
		if (r < 0) {
			fprintf(stderr, "sc_list_files() failed: %s\n", sc_strerror(r));
			return 1;
		}
		if (r == 0) {
			printf("Empty directory\n");
		} else
		for (i = 0; i < r/2; i++) {
			sc_path_t tmppath;

			memset(&tmppath, 0, sizeof(tmppath));
			memcpy(&tmppath, &path, sizeof(path));
			memcpy(tmppath.value + tmppath.len, files + 2*i, 2);
			tmppath.len += 2;
			enum_dir(tmppath, depth + 1);
		}
	}
	return 0;
}

static int list_files(void)
{
	sc_path_t path;
	int r;

	sc_format_path("3F00", &path);
	r = enum_dir(path, 0);
	return r;
}

static int send_apdu(void)
{
	sc_apdu_t apdu;
	u8 buf[SC_MAX_APDU_BUFFER_SIZE],
	  rbuf[SC_MAX_APDU_BUFFER_SIZE];
	size_t len0, r;
	int c;

	for (c = 0; c < opt_apdu_count; c++) {
		len0 = sizeof(buf);
		sc_hex_to_bin(opt_apdus[c], buf, &len0);

		r = sc_bytes2apdu(card->ctx, buf, len0, &apdu);
		if (r) {
			fprintf(stderr, "Invalid APDU: %s\n", sc_strerror(r));
			return 2;
		}

		apdu.resp = rbuf;
		apdu.resplen = sizeof(rbuf);

		printf("Sending: ");
		for (r = 0; r < len0; r++)
			printf("%02X ", buf[r]);
		printf("\n");
		r = sc_transmit_apdu(card, &apdu);
		if (r) {
			fprintf(stderr, "APDU transmit failed: %s\n", sc_strerror(r));
			return 1;
		}
		printf("Received (SW1=0x%02X, SW2=0x%02X)%s\n", apdu.sw1, apdu.sw2,
		      apdu.resplen ? ":" : "");
		if (apdu.resplen)
			util_hex_dump_asc(stdout, apdu.resp, apdu.resplen, -1);
	}
	return 0;
}

static void print_serial(sc_card_t *in_card)
{
	int r;
	sc_serial_number_t serial;

	r = sc_card_ctl(in_card, SC_CARDCTL_GET_SERIALNR, &serial);
	if (r)
		fprintf(stderr, "sc_card_ctl(*, SC_CARDCTL_GET_SERIALNR, *) failed\n");
	else
		util_hex_dump_asc(stdout, serial.value, serial.len, -1);
}

static int list_algorithms(void)
{
	int i;
	const char *aname = "unknown";

	const id2str_t alg_type_names[] = {
		{ SC_ALGORITHM_RSA,       "rsa"       },
		{ SC_ALGORITHM_DSA,       "dsa"       },
		{ SC_ALGORITHM_EC,        "ec"        },
		{ SC_ALGORITHM_GOSTR3410, "gostr3410" },
		{ SC_ALGORITHM_DES,       "des"       },
		{ SC_ALGORITHM_3DES,      "3des"      },
		{ SC_ALGORITHM_GOST,      "gost"      },
		{ SC_ALGORITHM_MD5,       "md5"       },
		{ SC_ALGORITHM_SHA1,      "sha1"      },
		{ SC_ALGORITHM_GOSTR3411, "gostr3411" },
		{ SC_ALGORITHM_PBKDF2,    "pbkdf2"    },
		{ SC_ALGORITHM_PBES2,     "pbes2"     },
		{ 0, NULL }
	};
	const id2str_t alg_flag_names[] = {
		{ SC_ALGORITHM_ONBOARD_KEY_GEN, "onboard key generation" },
		{ SC_ALGORITHM_NEED_USAGE,      "needs usage"            },
		{ 0, NULL }
	};
	const id2str_t rsa_flag_names[] = {
		{ SC_ALGORITHM_RSA_PAD_PKCS1,      "pkcs1"     },
		{ SC_ALGORITHM_RSA_PAD_ANSI,       "ansi"      },
		{ SC_ALGORITHM_RSA_PAD_ISO9796,    "iso9796"   },
		{ SC_ALGORITHM_RSA_HASH_SHA1,      "sha1"      },
		{ SC_ALGORITHM_RSA_HASH_MD5,       "MD5"       },
		{ SC_ALGORITHM_RSA_HASH_MD5_SHA1,  "md5-sha1"  },
		{ SC_ALGORITHM_RSA_HASH_RIPEMD160, "ripemd160" },
		{ SC_ALGORITHM_RSA_HASH_SHA256,    "sha256"    },
		{ SC_ALGORITHM_RSA_HASH_SHA384,    "sha384"    },
		{ SC_ALGORITHM_RSA_HASH_SHA512,    "sha512"    },
		{ SC_ALGORITHM_RSA_HASH_SHA224,    "sha224"    },
		{ 0, NULL }
	};

	if (verbose)
		printf("Card supports %d algorithm(s)\n\n",card->algorithm_count);

	for (i=0; i < card->algorithm_count; i++) {
		int j;

		/* find algorithm name */
		for (j = 0; alg_type_names[j].str != NULL; j++) {
			if (card->algorithms[i].algorithm == alg_type_names[j].id) {
				aname = alg_type_names[j].str;
				break;
			}
		}

		printf("Algorithm: %s\n", aname);
		printf("Key length: %d\n", card->algorithms[i].key_length);
		printf("Flags:");

		/* print general flags */
		for (j = 0; alg_flag_names[j].str != NULL; j++)
			if (card->algorithms[i].flags & alg_flag_names[j].id)
				printf(" %s", alg_flag_names[j].str);

		/* print RSA spcific flags */
		if ( card->algorithms[i].algorithm == SC_ALGORITHM_RSA) {
			int padding = card->algorithms[i].flags
					& SC_ALGORITHM_RSA_PADS;
			int hashes =  card->algorithms[i].flags
					& SC_ALGORITHM_RSA_HASHES;

			/* print RSA padding flags */
			printf(" padding (");
			for (j = 0; rsa_flag_names[j].str != NULL; j++)
				if (padding & rsa_flag_names[j].id)
					printf(" %s", rsa_flag_names[j].str);
			if (padding == SC_ALGORITHM_RSA_PAD_NONE)
				printf(" none");
			printf(" ) ");
			/* print RSA hash flags */
			printf("hashes (");
			for (j = 0; rsa_flag_names[j].str != NULL; j++)
				if (hashes & rsa_flag_names[j].id)
					printf(" %s", rsa_flag_names[j].str);
			if (hashes == SC_ALGORITHM_RSA_HASH_NONE)
				printf(" none");
			printf(" )");
		}
		printf("\n");
		if (card->algorithms[i].algorithm == SC_ALGORITHM_RSA
			&& card->algorithms[i].u._rsa.exponent) {
			printf("RSA public exponent: %lu\n", (unsigned long)
				card->algorithms[i].u._rsa.exponent);
		}

		if (i < card->algorithm_count)
			printf("\n");
	}
	return 0;
}

int main(int argc, char * const argv[])
{
	int err = 0, r, c, long_optind = 0;
	int do_info = 0;
	int do_get_conf_entry = 0;
	int do_set_conf_entry = 0;
	int do_list_readers = 0;
	int do_list_drivers = 0;
	int do_list_files = 0;
	int do_send_apdu = 0;
	int do_print_atr = 0;
	int do_print_version = 0;
	int do_print_serial = 0;
	int do_print_name = 0;
	int do_list_algorithms = 0;
	int action_count = 0;
	const char *opt_driver = NULL;
	const char *opt_conf_entry = NULL;
	char **p;
	sc_context_param_t ctx_param;

	setbuf(stderr, NULL);
	setbuf(stdout, NULL);

	while (1) {
		c = getopt_long(argc, argv, "inlG:S:fr:vs:Dc:aw", options, &long_optind);
		if (c == -1)
			break;
		if (c == '?')
			util_print_usage_and_die(app_name, options, option_help, NULL);
		switch (c) {
		case 'i':
			do_info = 1;
			action_count++;
			break;
		case 'G':
			do_get_conf_entry = 1;
			opt_conf_entry = optarg;
			action_count++;
			break;
		case 'S':
			do_set_conf_entry = 1;
			opt_conf_entry = optarg;
			action_count++;
			break;
		case 'l':
			do_list_readers = 1;
			action_count++;
			break;
		case 'D':
			do_list_drivers = 1;
			action_count++;
			break;
		case 'f':
			do_list_files = 1;
			action_count++;
			break;
		case 's':
			p = (char **) realloc(opt_apdus,
					(opt_apdu_count + 1) * sizeof(char *));
			if (!p) {
				fprintf(stderr, "Not enough memory\n");
				err = 1;
				goto end;
			}
			opt_apdus = p;
			opt_apdus[opt_apdu_count] = optarg;
			do_send_apdu++;
			if (opt_apdu_count == 0)
				action_count++;
			opt_apdu_count++;
			break;
		case 'a':
			do_print_atr = 1;
			action_count++;
			break;
		case 'n':
			do_print_name = 1;
			action_count++;
			break;
		case 'r':
			opt_reader = optarg;
			break;
		case 'v':
			verbose++;
			break;
		case OPT_VERSION:
			do_print_version = 1;
			action_count++;
			break;
		case 'c':
			opt_driver = optarg;
			break;
		case 'w':
			opt_wait = 1;
			break;
		case OPT_SERIAL:
			do_print_serial = 1;
			action_count++;
			break;
		case OPT_LIST_ALG:
			do_list_algorithms = 1;
			action_count++;
			break;
		}
	}
	if (action_count == 0)
		util_print_usage_and_die(app_name, options, option_help, NULL);

	if (do_print_version)   {
		printf("%s\n", OPENSC_SCM_REVISION);
		action_count--;
	}

	if (do_info) {
		opensc_info();
		action_count--;
	}

	memset(&ctx_param, 0, sizeof(ctx_param));
	ctx_param.ver      = 0;
	ctx_param.app_name = app_name;

	r = sc_context_create(&ctx, &ctx_param);
	if (r) {
		fprintf(stderr, "Failed to establish context: %s\n", sc_strerror(r));
		return 1;
	}

	ctx->flags |= SC_CTX_FLAG_ENABLE_DEFAULT_DRIVER;

	if (verbose > 1) {
		ctx->debug = verbose;
		sc_ctx_log_to_file(ctx, "stderr");
	}

	if (do_get_conf_entry) {
		if ((err = opensc_get_conf_entry (opt_conf_entry)))
			goto end;
		action_count--;
	}
	if (do_set_conf_entry) {
		if ((err = opensc_set_conf_entry (opt_conf_entry)))
			goto end;
		action_count--;
	}
	if (do_list_readers) {
		if ((err = list_readers()))
			goto end;
		action_count--;
	}
	if (do_list_drivers) {
		if ((err = list_drivers()))
			goto end;
		action_count--;
	}
	if (action_count <= 0)
		goto end;

	if (opt_driver != NULL) {
		err = sc_set_card_driver(ctx, opt_driver);
		if (err) {
			fprintf(stderr, "Driver '%s' not found!\n", opt_driver);
			err = 1;
			goto end;
		}
	}

	err = util_connect_card(ctx, &card, opt_reader, opt_wait, verbose);
	if (err)
		goto end;

	if (do_print_atr) {
		if (verbose) {
			printf("Card ATR:\n");
			util_hex_dump_asc(stdout, card->atr.value, card->atr.len, -1);
		} else {
			char tmp[SC_MAX_ATR_SIZE*3];
			sc_bin_to_hex(card->atr.value, card->atr.len, tmp, sizeof(tmp) - 1, ':');
			fprintf(stdout,"%s\n",tmp);
		}
		action_count--;
	}
	if (do_print_serial) {
		if (verbose)
			printf("Card serial number:");
		print_serial(card);
		action_count--;
	}
	if (do_print_name) {
		if (verbose)
			printf("Card name: ");
		printf("%s\n", card->name);
		action_count--;
	}
	if (do_send_apdu) {
		if ((err = send_apdu()))
			goto end;
		action_count--;
	}

	if (do_list_files) {
		if ((err = list_files()))
			goto end;
		action_count--;
	}

	if (do_list_algorithms) {
		if ((err = list_algorithms()))
			goto end;
		action_count--;
	}
end:
	if (card) {
		sc_unlock(card);
		sc_disconnect_card(card);
	}
	if (ctx)
		sc_release_context(ctx);
	return err;
}
