changeset 22:1b1468869ccf

new trimmed fc-uicc-tool is here
author Mychaela Falconia <falcon@freecalypso.org>
date Fri, 12 Feb 2021 04:34:53 +0000
parents d4dc86195382
children 2e4d02c1ee2d
files .hgignore uicc/Makefile uicc/dispatch.c uicc/dumpdir.c uicc/hlread.c uicc/main.c uicc/readcmd.c uicc/readops.c uicc/script.c uicc/select.c uicc/writecmd.c uicc/writeops.c
diffstat 12 files changed, 964 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- a/.hgignore	Fri Feb 12 04:16:35 2021 +0000
+++ b/.hgignore	Fri Feb 12 04:34:53 2021 +0000
@@ -3,3 +3,5 @@
 \.[oa]$
 
 ^simtool/fc-simtool$
+
+^uicc/fc-uicc-tool$
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/uicc/Makefile	Fri Feb 12 04:34:53 2021 +0000
@@ -0,0 +1,19 @@
+CC=	gcc
+CFLAGS=	-O2 -I/usr/include/PCSC -I../libcommon
+PROG=	fc-uicc-tool
+OBJS=	dispatch.o dumpdir.o hlread.o main.o readcmd.o readops.o script.o \
+	select.o writecmd.o writeops.o
+LIBS=	../libcommon/libcommon.a
+INSTBIN=/opt/freecalypso/bin
+
+all:	${PROG}
+
+${PROG}:	${OBJS} ${LIBS}
+	${CC} ${CFLAGS} -o $@ ${OBJS} ${LIBS} -lpcsclite
+
+install:
+	mkdir -p ${INSTBIN}
+	install -c ${PROG} ${INSTBIN}
+
+clean:
+	rm -f ${PROG} *.o
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/uicc/dispatch.c	Fri Feb 12 04:34:53 2021 +0000
@@ -0,0 +1,141 @@
+/*
+ * This module implements the command dispatch for fc-uicc-tool
+ */
+
+#include <ctype.h>
+#include <stdio.h>
+#include <string.h>
+#include <strings.h>
+#include <stdlib.h>
+
+extern int cmd_dir();
+extern int cmd_exec();
+extern int cmd_iccid();
+extern int cmd_readbin();
+extern int cmd_readef();
+extern int cmd_readrec();
+extern int cmd_select();
+extern int cmd_select_aid();
+extern int cmd_select_isim();
+extern int cmd_select_usim();
+extern int cmd_update_bin();
+extern int cmd_update_bin_imm();
+extern int cmd_update_rec();
+
+extern int display_sim_resp_in_hex();
+extern int good_exit();
+
+static struct cmdtab {
+	char *cmd;
+	int minargs;
+	int maxargs;
+	int (*func)();
+} cmdtab[] = {
+	{"dir", 0, 0, cmd_dir},
+	{"exec", 1, 1, cmd_exec},
+	{"exit", 0, 0, good_exit},
+	{"iccid", 0, 0, cmd_iccid},
+	{"quit", 0, 0, good_exit},
+	{"readbin", 2, 2, cmd_readbin},
+	{"readef", 1, 1, cmd_readef},
+	{"readrec", 1, 2, cmd_readrec},
+	{"select", 1, 1, cmd_select},
+	{"select-aid", 1, 1, cmd_select_aid},
+	{"select-isim", 0, 0, cmd_select_isim},
+	{"select-usim", 0, 0, cmd_select_usim},
+	{"sim-resp", 0, 0, display_sim_resp_in_hex},
+	{"update-bin", 2, 2, cmd_update_bin},
+	{"update-bin-imm", 2, 2, cmd_update_bin_imm},
+	{"update-rec", 2, 2, cmd_update_rec},
+	{0, 0, 0, 0}
+};
+
+simtool_dispatch_cmd(cmd, is_script)
+	char *cmd;
+{
+	char *argv[10];
+	char *cp, **ap;
+	struct cmdtab *tp;
+
+	for (cp = cmd; isspace(*cp); cp++)
+		;
+	if (!*cp || *cp == '#')
+		return(0);
+	if (is_script)
+		printf("Script command: %s\n", cp);
+	argv[0] = cp;
+	while (*cp && !isspace(*cp))
+		cp++;
+	if (*cp)
+		*cp++ = '\0';
+	for (tp = cmdtab; tp->cmd; tp++)
+		if (!strcmp(tp->cmd, argv[0]))
+			break;
+	if (!tp->func) {
+		fprintf(stderr, "error: no such command\n");
+		return(-1);
+	}
+	for (ap = argv + 1; ; ) {
+		while (isspace(*cp))
+			cp++;
+		if (!*cp || *cp == '#')
+			break;
+		if (ap - argv - 1 >= tp->maxargs) {
+			fprintf(stderr, "error: too many arguments\n");
+			return(-1);
+		}
+		if (*cp == '"') {
+			*ap++ = ++cp;
+			for (;;) {
+				if (!*cp) {
+unterm_qstring:				fprintf(stderr,
+					"error: unterminated quoted string\n");
+					return(-1);
+				}
+				if (*cp == '"')
+					break;
+				if (*cp++ == '\\') {
+					if (!*cp)
+						goto unterm_qstring;
+					cp++;
+				}
+			}
+			*cp++ = '\0';
+		} else {
+			*ap++ = cp;
+			while (*cp && !isspace(*cp))
+				cp++;
+			if (*cp)
+				*cp++ = '\0';
+		}
+	}
+	if (ap - argv - 1 < tp->minargs) {
+		fprintf(stderr, "error: too few arguments\n");
+		return(-1);
+	}
+	*ap = 0;
+	return tp->func(ap - argv, argv);
+}
+
+dispatch_ready_argv(argc, argv)
+	char **argv;
+{
+	struct cmdtab *tp;
+
+	for (tp = cmdtab; tp->cmd; tp++)
+		if (!strcmp(tp->cmd, argv[0]))
+			break;
+	if (!tp->func) {
+		fprintf(stderr, "error: no such command\n");
+		return(-1);
+	}
+	if (argc - 1 > tp->maxargs) {
+		fprintf(stderr, "error: too many arguments\n");
+		return(-1);
+	}
+	if (argc - 1 < tp->minargs) {
+		fprintf(stderr, "error: too few arguments\n");
+		return(-1);
+	}
+	return tp->func(argc, argv);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/uicc/dumpdir.c	Fri Feb 12 04:34:53 2021 +0000
@@ -0,0 +1,38 @@
+/*
+ * This module implements the dump of EF_DIR.
+ */
+
+#include <sys/types.h>
+#include <stdio.h>
+#include "file_id.h"
+
+cmd_dir()
+{
+	int rc;
+	unsigned record_len, record_count;
+	unsigned recno;
+
+	rc = select_op(FILEID_MF);
+	if (rc < 0)
+		return(rc);
+	rc = select_op(EF_DIR);
+	if (rc < 0)
+		return(rc);
+	rc = select_resp_get_linear_fixed(&record_len, &record_count);
+	if (rc < 0)
+		return(rc);
+	if (record_len < 5) {
+		fprintf(stderr, "error: EF_DIR record length is too short\n");
+		return(-1);
+	}
+	for (recno = 1; recno <= record_count; recno++) {
+		rc = readrec_op(recno, 0x04, record_len);
+		if (rc < 0)
+			return(rc);
+		if (check_simresp_all_blank())
+			continue;
+		printf("Record #%u:\n", recno);
+		dump_efdir_record();
+	}
+	return(0);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/uicc/hlread.c	Fri Feb 12 04:34:53 2021 +0000
@@ -0,0 +1,36 @@
+/*
+ * This module implements some high-level or user-friendly read commands.
+ */
+
+#include <sys/types.h>
+#include <stdio.h>
+#include "simresp.h"
+#include "file_id.h"
+
+cmd_iccid()
+{
+	int rc;
+	unsigned len;
+	char buf[21];
+
+	rc = select_op(FILEID_MF);
+	if (rc < 0)
+		return(rc);
+	rc = select_op(EF_ICCID);
+	if (rc < 0)
+		return(rc);
+	rc = select_resp_get_transparent(&len);
+	if (rc < 0)
+		return(rc);
+	if (len != 10) {
+		fprintf(stderr, "error: expected transparent EF of 10 bytes\n");
+		return(-1);
+	}
+	rc = readbin_op(0, 10);
+	if (rc < 0)
+		return(rc);
+	decode_reversed_nibbles(sim_resp_data, 10, buf);
+	buf[20] = '\0';
+	printf("%s\n", buf);
+	return(0);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/uicc/main.c	Fri Feb 12 04:34:53 2021 +0000
@@ -0,0 +1,35 @@
+#include <sys/types.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <pcsclite.h>
+#include <winscard.h>
+#include "cardif.h"
+
+main(argc, argv)
+	char **argv;
+{
+	char command[512];
+	int rc;
+
+	setup_pcsc_context();
+	get_reader_name();
+	printf("Card reader name: %s\n", reader_name_buf);
+	connect_to_card();
+	retrieve_atr();
+	if (argc >= 2) {
+		rc = dispatch_ready_argv(argc - 1, argv + 1);
+		if (rc)
+			error_exit();
+		else
+			good_exit();
+	}
+	for (;;) {
+		if (isatty(0)) {
+			fputs("uicc> ", stdout);
+			fflush(stdout);
+		}
+		if (!fgets(command, sizeof command, stdin))
+			good_exit();
+		simtool_dispatch_cmd(command, 0);
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/uicc/readcmd.c	Fri Feb 12 04:34:53 2021 +0000
@@ -0,0 +1,99 @@
+#include <sys/types.h>
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include "simresp.h"
+
+extern unsigned last_sel_file_record_len;
+
+cmd_readbin(argc, argv)
+	char **argv;
+{
+	unsigned offset, len;
+	int rc;
+
+	offset = strtoul(argv[1], 0, 0);
+	if (offset > 0x7FFF) {
+		fprintf(stderr, "error: offset argument is out of range\n");
+		return(-1);
+	}
+	len = strtoul(argv[2], 0, 0);
+	if (len < 1 || len > 256) {
+		fprintf(stderr, "error: length argument is out of range\n");
+		return(-1);
+	}
+	rc = readbin_op(offset, len);
+	if (rc < 0)
+		return(rc);
+	display_sim_resp_in_hex();
+	return(0);
+}
+
+cmd_readrec(argc, argv)
+	char **argv;
+{
+	unsigned recno, len;
+	int rc;
+
+	recno = strtoul(argv[1], 0, 0);
+	if (recno < 1 || recno > 255) {
+		fprintf(stderr,
+			"error: record number argument is out of range\n");
+		return(-1);
+	}
+	if (argv[2]) {
+		len = strtoul(argv[2], 0, 0);
+		if (len < 1 || len > 255) {
+			fprintf(stderr,
+				"error: length argument is out of range\n");
+			return(-1);
+		}
+	} else {
+		if (!last_sel_file_record_len) {
+			fprintf(stderr,
+			"error: no current file record length is available\n");
+			return(-1);
+		}
+		len = last_sel_file_record_len;
+	}
+	rc = readrec_op(recno, 0x04, len);
+	if (rc < 0)
+		return(rc);
+	display_sim_resp_in_hex();
+	return(0);
+}
+
+cmd_readef(argc, argv)
+	char **argv;
+{
+	int file_id, rc;
+	unsigned file_len, readlen;
+
+	if (isxdigit(argv[1][0]) && isxdigit(argv[1][1]) &&
+	    isxdigit(argv[1][2]) && isxdigit(argv[1][3]) && !argv[1][4])
+		file_id = strtoul(argv[1], 0, 16);
+	else
+		file_id = find_symbolic_file_name(argv[1]);
+	if (file_id < 0) {
+		fprintf(stderr,
+"error: file ID argument is not a hex value or a recognized symbolic name\n");
+		return(-1);
+	}
+	rc = select_op(file_id);
+	if (rc < 0)
+		return(rc);
+	rc = select_resp_get_transparent(&file_len);
+	if (rc < 0)
+		return(rc);
+	printf("Transparent EF of %u byte(s)\n", file_len);
+	if (!file_len)
+		return(0);
+	readlen = file_len;
+	if (readlen > 256)
+		readlen = 256;
+	rc = readbin_op(0, readlen);
+	if (rc < 0)
+		return(rc);
+	display_sim_resp_in_hex();
+	return(0);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/uicc/readops.c	Fri Feb 12 04:34:53 2021 +0000
@@ -0,0 +1,62 @@
+#include <sys/types.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include "simresp.h"
+
+readbin_op(offset, len)
+	unsigned offset, len;
+{
+	u_char cmd[5];
+	int rc;
+
+	/* READ BINARY command APDU */
+	cmd[0] = 0x00;
+	cmd[1] = 0xB0;
+	cmd[2] = offset >> 8;
+	cmd[3] = offset;
+	cmd[4] = len;
+	rc = apdu_exchange(cmd, 5);
+	if (rc < 0)
+		return(rc);
+	if (sim_resp_sw != 0x9000) {
+		fprintf(stderr, "bad SW response to READ BINARY: %04X\n",
+			sim_resp_sw);
+		return(-1);
+	}
+	if (sim_resp_data_len != len) {
+		fprintf(stderr,
+			"error: READ BINARY returned %u bytes, expected %u\n",
+			sim_resp_data_len, len);
+		return(-1);
+	}
+	return(0);
+}
+
+readrec_op(recno, mode, len)
+	unsigned recno, mode, len;
+{
+	u_char cmd[5];
+	int rc;
+
+	/* READ RECORD command APDU */
+	cmd[0] = 0x00;
+	cmd[1] = 0xB2;
+	cmd[2] = recno;
+	cmd[3] = mode;
+	cmd[4] = len;
+	rc = apdu_exchange(cmd, 5);
+	if (rc < 0)
+		return(rc);
+	if (sim_resp_sw != 0x9000) {
+		fprintf(stderr, "bad SW response to READ RECORD: %04X\n",
+			sim_resp_sw);
+		return(-1);
+	}
+	if (sim_resp_data_len != len) {
+		fprintf(stderr,
+			"error: READ RECORD returned %u bytes, expected %u\n",
+			sim_resp_data_len, len);
+		return(-1);
+	}
+	return(0);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/uicc/script.c	Fri Feb 12 04:34:53 2021 +0000
@@ -0,0 +1,37 @@
+/*
+ * This module implements the exec command, which is our scripting facility.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+
+cmd_exec(argc, argv)
+	char **argv;
+{
+	FILE *f;
+	char linebuf[512], *cp;
+	int lineno, retval = 0;
+
+	f = fopen(argv[1], "r");
+	if (!f) {
+		perror(argv[1]);
+		return(-1);
+	}
+	for (lineno = 1; fgets(linebuf, sizeof linebuf, f); lineno++) {
+		cp = index(linebuf, '\n');
+		if (!cp) {
+			fprintf(stderr, "%s line %d: missing newline\n",
+				argv[1], lineno);
+			fclose(f);
+			return(-1);
+		}
+		*cp = '\0';
+		retval = simtool_dispatch_cmd(linebuf, 1);
+		if (retval)
+			break;
+	}
+	fclose(f);
+	return(retval);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/uicc/select.c	Fri Feb 12 04:34:53 2021 +0000
@@ -0,0 +1,369 @@
+#include <sys/types.h>
+#include <ctype.h>
+#include <string.h>
+#include <strings.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include "simresp.h"
+
+u_char std_aid_usim[7] = {0xA0, 0x00, 0x00, 0x00, 0x87, 0x10, 0x02};
+u_char std_aid_isim[7] = {0xA0, 0x00, 0x00, 0x00, 0x87, 0x10, 0x04};
+
+unsigned last_sel_file_record_len;
+
+select_op(file_id)
+	unsigned file_id;
+{
+	u_char cmd[7];
+	int rc;
+	unsigned expect_resp_len;
+
+	last_sel_file_record_len = 0;
+	/* SELECT command APDU */
+	cmd[0] = 0x00;
+	cmd[1] = 0xA4;
+	cmd[2] = 0x00;
+	cmd[3] = 0x04;
+	cmd[4] = 2;
+	cmd[5] = file_id >> 8;
+	cmd[6] = file_id;
+	rc = apdu_exchange(cmd, 7);
+	if (rc < 0)
+		return(rc);
+	if ((sim_resp_sw & 0xFF00) != 0x6100) {
+		fprintf(stderr,
+		"error or unexpected SW response to SELECT of 0x%04X: %04X\n",
+			file_id, sim_resp_sw);
+		return(-1);
+	}
+	expect_resp_len = sim_resp_sw & 0xFF;
+	/* GET RESPONSE follow-up */
+	cmd[1] = 0xC0;
+	cmd[2] = 0;
+	cmd[3] = 0;
+	cmd[4] = expect_resp_len;
+	rc = apdu_exchange(cmd, 5);
+	if (rc < 0)
+		return(rc);
+	if (sim_resp_sw != 0x9000) {
+		fprintf(stderr,
+			"bad SW resp to GET RESPONSE after SELECT: %04X\n",
+			sim_resp_sw);
+		return(-1);
+	}
+	if (sim_resp_data_len != expect_resp_len) {
+		fprintf(stderr,
+	"error: GET RESPONSE after SELECT returned %u bytes, expected %u\n",
+			sim_resp_data_len, expect_resp_len);
+		return(-1);
+	}
+	return(0);
+}
+
+select_aid_op(aid, aid_len)
+	u_char *aid;
+	unsigned aid_len;
+{
+	u_char cmd[21];
+	int rc;
+	unsigned expect_resp_len;
+
+	last_sel_file_record_len = 0;
+	/* SELECT command APDU */
+	cmd[0] = 0x00;
+	cmd[1] = 0xA4;
+	cmd[2] = 0x04;
+	cmd[3] = 0x04;
+	cmd[4] = aid_len;
+	bcopy(aid, cmd + 5, aid_len);
+	rc = apdu_exchange(cmd, aid_len + 5);
+	if (rc < 0)
+		return(rc);
+	if ((sim_resp_sw & 0xFF00) != 0x6100) {
+		fprintf(stderr,
+		"error or unexpected SW response to SELECT by AID: %04X\n",
+			sim_resp_sw);
+		return(-1);
+	}
+	expect_resp_len = sim_resp_sw & 0xFF;
+	/* GET RESPONSE follow-up */
+	cmd[1] = 0xC0;
+	cmd[2] = 0;
+	cmd[3] = 0;
+	cmd[4] = expect_resp_len;
+	rc = apdu_exchange(cmd, 5);
+	if (rc < 0)
+		return(rc);
+	if (sim_resp_sw != 0x9000) {
+		fprintf(stderr,
+			"bad SW resp to GET RESPONSE after SELECT: %04X\n",
+			sim_resp_sw);
+		return(-1);
+	}
+	if (sim_resp_data_len != expect_resp_len) {
+		fprintf(stderr,
+	"error: GET RESPONSE after SELECT returned %u bytes, expected %u\n",
+			sim_resp_data_len, expect_resp_len);
+		return(-1);
+	}
+	return(0);
+}
+
+select_resp_header_check(ret_offset, ret_length)
+	unsigned *ret_offset, *ret_length;
+{
+	unsigned offset, len;
+
+	if (sim_resp_data_len < 2) {
+tooshort:	fprintf(stderr, "error: SELECT response is too short\n");
+		return(-1);
+	}
+	if (sim_resp_data[0] != 0x62) {
+		fprintf(stderr, "error: SELECT response first byte != 0x62\n");
+		return(-1);
+	}
+	len = sim_resp_data[1];
+	if (len <= 0x7F) {
+		offset = 2;
+return_check:	if (offset + len > sim_resp_data_len)
+			goto tooshort;
+		if (ret_offset)
+			*ret_offset = offset;
+		if (ret_length)
+			*ret_length = len;
+		return(0);
+	}
+	if (len != 0x81) {
+		fprintf(stderr, "SELECT response: first length byte is bad\n");
+		return(-1);
+	}
+	if (sim_resp_data_len < 3)
+		goto tooshort;
+	len = sim_resp_data[2];
+	offset = 3;
+	goto return_check;
+}
+
+static void
+check_for_record_struct(tlv)
+	u_char *tlv;
+{
+	unsigned reclen;
+
+	if (tlv[1] != 5)
+		return;
+	if (tlv[2] & 0x80)
+		return;
+	if ((tlv[2] & 0x38) == 0x38)
+		return;
+	if ((tlv[2] & 0x03) != 0x02)
+		return;
+	reclen = (tlv[4] << 8) | tlv[5];
+	if (reclen < 1 || reclen > 255)
+		return;
+	last_sel_file_record_len = reclen;
+}
+
+parse_and_display_select_response()
+{
+	unsigned offset, totlen, reclen, n;
+	u_char *dp, *endp;
+	int rc;
+
+	rc = select_resp_header_check(&offset, &totlen);
+	if (rc < 0)
+		return(rc);
+	dp = sim_resp_data + offset;
+	endp = sim_resp_data + offset + totlen;
+	while (dp < endp) {
+		if (endp - dp < 2) {
+trunc_error:		fprintf(stderr,
+			"error: truncated TLV record in SELECT response\n");
+			return(-1);
+		}
+		if ((dp[0] & 0x1F) == 0x1F) {
+			fprintf(stderr,
+		"error: extended tag not supported in SELECT response\n");
+			return(-1);
+		}
+		if (dp[1] & 0x80) {
+			fprintf(stderr,
+		"error: extended length not supported in SELECT response\n");
+			return(-1);
+		}
+		reclen = dp[1] + 2;
+		if (endp - dp < reclen)
+			goto trunc_error;
+		if (dp[0] == 0x82)
+			check_for_record_struct(dp);
+		for (n = 0; n < reclen; n++) {
+			if (n)
+				putchar(' ');
+			printf("%02X", *dp++);
+		}
+		putchar('\n');
+	}
+	return(0);
+}
+
+cmd_select(argc, argv)
+	char **argv;
+{
+	int file_id, rc;
+
+	if (isxdigit(argv[1][0]) && isxdigit(argv[1][1]) &&
+	    isxdigit(argv[1][2]) && isxdigit(argv[1][3]) && !argv[1][4])
+		file_id = strtoul(argv[1], 0, 16);
+	else
+		file_id = find_symbolic_file_name(argv[1]);
+	if (file_id < 0) {
+		fprintf(stderr,
+"error: file ID argument is not a hex value or a recognized symbolic name\n");
+		return(-1);
+	}
+	rc = select_op(file_id);
+	if (rc < 0)
+		return(rc);
+	return parse_and_display_select_response();
+}
+
+cmd_select_aid(argc, argv)
+	char **argv;
+{
+	u_char aid[16];
+	unsigned aid_len;
+	int rc;
+
+	rc = decode_hex_data_from_string(argv[1], aid, 1, 16);
+	if (rc < 0)
+		return(rc);
+	aid_len = rc;
+	rc = select_aid_op(aid, aid_len);
+	if (rc < 0)
+		return(rc);
+	return parse_and_display_select_response();
+}
+
+cmd_select_usim()
+{
+	int rc;
+
+	rc = select_aid_op(std_aid_usim, 7);
+	if (rc < 0)
+		return(rc);
+	return parse_and_display_select_response();
+}
+
+cmd_select_isim()
+{
+	int rc;
+
+	rc = select_aid_op(std_aid_isim, 7);
+	if (rc < 0)
+		return(rc);
+	return parse_and_display_select_response();
+}
+
+u_char *
+extract_select_resp_tag(sought_tag)
+	unsigned sought_tag;
+{
+	unsigned offset, totlen, reclen;
+	u_char *dp, *endp;
+	int rc;
+
+	rc = select_resp_header_check(&offset, &totlen);
+	if (rc < 0)
+		return(0);
+	dp = sim_resp_data + offset;
+	endp = sim_resp_data + offset + totlen;
+	while (dp < endp) {
+		if (endp - dp < 2) {
+trunc_error:		fprintf(stderr,
+			"error: truncated TLV record in SELECT response\n");
+			return(0);
+		}
+		if ((dp[0] & 0x1F) == 0x1F) {
+			fprintf(stderr,
+		"error: extended tag not supported in SELECT response\n");
+			return(0);
+		}
+		if (dp[1] & 0x80) {
+			fprintf(stderr,
+		"error: extended length not supported in SELECT response\n");
+			return(0);
+		}
+		reclen = dp[1] + 2;
+		if (endp - dp < reclen)
+			goto trunc_error;
+		if (dp[0] == sought_tag)
+			return(dp);
+		dp += reclen;
+	}
+	fprintf(stderr, "error: tag 0x%02X not found in SELECT response\n",
+		sought_tag);
+	return(0);
+}
+
+select_resp_get_transparent(lenp)
+	unsigned *lenp;
+{
+	u_char *tlv;
+
+	tlv = extract_select_resp_tag(0x82);
+	if (!tlv)
+		return(-1);
+	if (tlv[1] != 2) {
+bad_file_desc:	fprintf(stderr, "error: file type is not transparent EF\n");
+		return(-1);
+	}
+	if (tlv[2] & 0x80)
+		goto bad_file_desc;
+	if ((tlv[2] & 0x38) == 0x38)
+		goto bad_file_desc;
+	if ((tlv[2] & 0x07) != 0x01)
+		goto bad_file_desc;
+	tlv = extract_select_resp_tag(0x80);
+	if (!tlv)
+		return(-1);
+	if (tlv[1] != 2) {
+		fprintf(stderr,
+			"error: file size TLV element has wrong length\n");
+		return(-1);
+	}
+	if (lenp)
+		*lenp = (tlv[2] << 8) | tlv[3];
+	return(0);
+}
+
+select_resp_get_linear_fixed(rec_len_ret, rec_count_ret)
+	unsigned *rec_len_ret, *rec_count_ret;
+{
+	u_char *tlv;
+	unsigned reclen;
+
+	tlv = extract_select_resp_tag(0x82);
+	if (!tlv)
+		return(-1);
+	if (tlv[1] != 5) {
+bad_file_desc:	fprintf(stderr, "error: file type is not linear fixed EF\n");
+		return(-1);
+	}
+	if (tlv[2] & 0x80)
+		goto bad_file_desc;
+	if ((tlv[2] & 0x38) == 0x38)
+		goto bad_file_desc;
+	if ((tlv[2] & 0x07) != 0x02)
+		goto bad_file_desc;
+	reclen = (tlv[4] << 8) | tlv[5];
+	if (reclen < 1 || reclen > 255) {
+		fprintf(stderr,
+			"error: SELECT response gives invalid record length\n");
+		return(-1);
+	}
+	if (rec_len_ret)
+		*rec_len_ret = reclen;
+	if (rec_count_ret)
+		*rec_count_ret = tlv[6];
+	return(0);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/uicc/writecmd.c	Fri Feb 12 04:34:53 2021 +0000
@@ -0,0 +1,70 @@
+#include <sys/types.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+extern unsigned last_sel_file_record_len;
+
+cmd_update_bin(argc, argv)
+	char **argv;
+{
+	unsigned offset, len;
+	u_char data[255];
+	int rc;
+
+	offset = strtoul(argv[1], 0, 0);
+	if (offset > 0x7FFF) {
+		fprintf(stderr, "error: offset argument is out of range\n");
+		return(-1);
+	}
+	rc = read_hex_data_file(argv[2], data);
+	if (rc < 0)
+		return(rc);
+	len = rc;
+	return update_bin_op(offset, data, len);
+}
+
+cmd_update_bin_imm(argc, argv)
+	char **argv;
+{
+	unsigned offset, len;
+	u_char data[255];
+	int rc;
+
+	offset = strtoul(argv[1], 0, 0);
+	if (offset > 0x7FFF) {
+		fprintf(stderr, "error: offset argument is out of range\n");
+		return(-1);
+	}
+	rc = decode_hex_data_from_string(argv[2], data, 1, 255);
+	if (rc < 0)
+		return(rc);
+	len = rc;
+	return update_bin_op(offset, data, len);
+}
+
+cmd_update_rec(argc, argv)
+	char **argv;
+{
+	unsigned recno;
+	u_char data[255];
+	int rc;
+
+	if (!last_sel_file_record_len) {
+		fprintf(stderr, "error: no record-based file selected\n");
+		return(-1);
+	}
+	recno = strtoul(argv[1], 0, 0);
+	if (recno < 1 || recno > 255) {
+		fprintf(stderr,
+			"error: record number argument is out of range\n");
+		return(-1);
+	}
+	rc = read_hex_data_file(argv[2], data);
+	if (rc < 0)
+		return(rc);
+	if (rc != last_sel_file_record_len) {
+		fprintf(stderr, "error: hex data length != EF record length\n");
+		return(-1);
+	}
+	return update_rec_op(recno, 0x04, data, last_sel_file_record_len);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/uicc/writeops.c	Fri Feb 12 04:34:53 2021 +0000
@@ -0,0 +1,56 @@
+#include <sys/types.h>
+#include <string.h>
+#include <strings.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include "simresp.h"
+
+update_bin_op(offset, data, len)
+	unsigned offset, len;
+	u_char *data;
+{
+	u_char cmd[260];
+	int rc;
+
+	/* UPDATE BINARY command APDU */
+	cmd[0] = 0x00;
+	cmd[1] = 0xD6;
+	cmd[2] = offset >> 8;
+	cmd[3] = offset;
+	cmd[4] = len;
+	bcopy(data, cmd + 5, len);
+	rc = apdu_exchange(cmd, len + 5);
+	if (rc < 0)
+		return(rc);
+	if (sim_resp_sw != 0x9000) {
+		fprintf(stderr, "bad SW response to UPDATE BINARY: %04X\n",
+			sim_resp_sw);
+		return(-1);
+	}
+	return(0);
+}
+
+update_rec_op(recno, mode, data, len)
+	unsigned recno, mode, len;
+	u_char *data;
+{
+	u_char cmd[260];
+	int rc;
+
+	/* UPDATE RECORD command APDU */
+	cmd[0] = 0x00;
+	cmd[1] = 0xDC;
+	cmd[2] = recno;
+	cmd[3] = mode;
+	cmd[4] = len;
+	bcopy(data, cmd + 5, len);
+	rc = apdu_exchange(cmd, len + 5);
+	if (rc < 0)
+		return(rc);
+	if (sim_resp_sw != 0x9000) {
+		fprintf(stderr, "bad SW response to UPDATE RECORD: %04X\n",
+			sim_resp_sw);
+		return(-1);
+	}
+	return(0);
+}