changeset 1:2071b28cd0c7

simtool: first refactored version
author Mychaela Falconia <falcon@freecalypso.org>
date Thu, 11 Feb 2021 23:04:28 +0000
parents f7145c77b7fb
children 5b69dfc789f2
files .hgignore simtool/Makefile simtool/a38.c simtool/chv.c simtool/curfile.c simtool/curfile.h simtool/dispatch.c simtool/dumpdir.c simtool/grcard1.c simtool/grcard2.c simtool/hlread.c simtool/main.c simtool/pbcommon.c simtool/pbdump.c simtool/pberase.c simtool/pbupdate.c simtool/readcmd.c simtool/readops.c simtool/saverestore.c simtool/script.c simtool/select.c simtool/sysmo.c simtool/telsum.c simtool/writecmd.c simtool/writeops.c
diffstat 25 files changed, 2846 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- a/.hgignore	Thu Feb 11 22:28:45 2021 +0000
+++ b/.hgignore	Thu Feb 11 23:04:28 2021 +0000
@@ -1,3 +1,5 @@
 syntax: regexp
 
 \.[oa]$
+
+^simtool/fc-simtool$
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/simtool/Makefile	Thu Feb 11 23:04:28 2021 +0000
@@ -0,0 +1,20 @@
+CC=	gcc
+CFLAGS=	-O2 -I/usr/include/PCSC -I../libcommon
+PROG=	fc-simtool
+OBJS=	a38.o chv.o curfile.o dispatch.o dumpdir.o grcard1.o grcard2.o hlread.o\
+	main.o pbcommon.o pbdump.o pberase.o pbupdate.o readcmd.o readops.o \
+	saverestore.o script.o select.o sysmo.o telsum.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/simtool/a38.c	Thu Feb 11 23:04:28 2021 +0000
@@ -0,0 +1,60 @@
+/*
+ * This module implements the a38 command for exercising
+ * the SIM's RUN GSM ALGORITHM operation.
+ */
+
+#include <sys/types.h>
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include "simresp.h"
+
+cmd_a38(argc, argv)
+	char **argv;
+{
+	u_char cmd[21];
+	int rc;
+
+	/* RUN GSM ALGORITHM command APDU */
+	cmd[0] = 0xA0;
+	cmd[1] = 0x88;
+	cmd[2] = 0;
+	cmd[3] = 0;
+	cmd[4] = 16;
+	rc = decode_hex_data_from_string(argv[1], cmd + 5, 16, 16);
+	if (rc < 0)
+		return(rc);
+	rc = apdu_exchange(cmd, 21);
+	if (rc < 0)
+		return(rc);
+	if (sim_resp_sw != 0x9F0C) {
+		fprintf(stderr,
+		"error or unexpected SW response to RUN GSM ALGO: %04X\n",
+			sim_resp_sw);
+		return(-1);
+	}
+	/* GET RESPONSE follow-up */
+	cmd[1] = 0xC0;
+	cmd[4] = 12;
+	rc = apdu_exchange(cmd, 5);
+	if (rc < 0)
+		return(rc);
+	if (sim_resp_sw != 0x9000) {
+		fprintf(stderr, "bad SW resp to GET RESPONSE: %04X\n",
+			sim_resp_sw);
+		return(-1);
+	}
+	if (sim_resp_data_len != 12) {
+		fprintf(stderr,
+			"error: GET RESPONSE returned %u bytes, expected 12\n",
+			sim_resp_data_len);
+		return(-1);
+	}
+	printf("SRES: %02X %02X %02X %02X\n", sim_resp_data[0],
+		sim_resp_data[1], sim_resp_data[2], sim_resp_data[3]);
+	printf("Kc: %02X %02X %02X %02X %02X %02X %02X %02X\n",
+		sim_resp_data[4], sim_resp_data[5], sim_resp_data[6],
+		sim_resp_data[7], sim_resp_data[8], sim_resp_data[9],
+		sim_resp_data[10], sim_resp_data[11]);
+	return(0);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/simtool/chv.c	Thu Feb 11 23:04:28 2021 +0000
@@ -0,0 +1,221 @@
+/*
+ * This module implements the standard set of CHV commands
+ * for GSM 11.11 SIMs.
+ */
+
+#include <sys/types.h>
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include "simresp.h"
+
+encode_pin_entry(arg, dest)
+	char *arg;
+	u_char *dest;
+{
+	unsigned n;
+
+	n = 0;
+	while (*arg) {
+		if (!isdigit(*arg)) {
+			fprintf(stderr,
+			"error: PIN argument contains a non-digit character\n");
+			return(-1);
+		}
+		if (n >= 8) {
+			fprintf(stderr, "error: PIN argument is too long\n");
+			return(-1);
+		}
+		*dest++ = *arg++;
+		n++;
+	}
+	for (; n < 8; n++)
+		*dest++ = 0xFF;
+	return(0);
+}
+
+cmd_verify_chv(argc, argv)
+	char **argv;
+{
+	u_char cmd[13];
+	int rc;
+
+	/* VERIFY CHV command APDU */
+	cmd[0] = 0xA0;
+	cmd[1] = 0x20;
+	cmd[2] = 0x00;
+	switch (argv[0][10]) {
+	case '1':
+		cmd[3] = 0x01;
+		break;
+	case '2':
+		cmd[3] = 0x02;
+		break;
+	default:
+		fprintf(stderr, "BUG in verify-chvN command\n");
+		return(-1);
+	}
+	cmd[4] = 8;
+	rc = encode_pin_entry(argv[1], cmd + 5);
+	if (rc < 0)
+		return(rc);
+	rc = apdu_exchange(cmd, 13);
+	if (rc < 0)
+		return(rc);
+	if (sim_resp_sw != 0x9000) {
+		fprintf(stderr, "bad SW response: %04X\n", sim_resp_sw);
+		return(-1);
+	}
+	return(0);
+}
+
+cmd_verify_ext(argc, argv)
+	char **argv;
+{
+	u_char cmd[13];
+	int rc;
+
+	/* VERIFY CHV command APDU */
+	cmd[0] = 0xA0;
+	cmd[1] = 0x20;
+	cmd[2] = 0x00;
+	cmd[3] = strtoul(argv[1], 0, 0);
+	cmd[4] = 8;
+	rc = encode_pin_entry(argv[2], cmd + 5);
+	if (rc < 0)
+		return(rc);
+	rc = apdu_exchange(cmd, 13);
+	if (rc < 0)
+		return(rc);
+	if (sim_resp_sw != 0x9000) {
+		fprintf(stderr, "bad SW response: %04X\n", sim_resp_sw);
+		return(-1);
+	}
+	return(0);
+}
+
+cmd_change_chv(argc, argv)
+	char **argv;
+{
+	u_char cmd[21];
+	int rc;
+
+	/* CHANGE CHV command APDU */
+	cmd[0] = 0xA0;
+	cmd[1] = 0x24;
+	cmd[2] = 0x00;
+	switch (argv[0][10]) {
+	case '1':
+		cmd[3] = 0x01;
+		break;
+	case '2':
+		cmd[3] = 0x02;
+		break;
+	default:
+		fprintf(stderr, "BUG in change-chvN command\n");
+		return(-1);
+	}
+	cmd[4] = 16;
+	rc = encode_pin_entry(argv[1], cmd + 5);
+	if (rc < 0)
+		return(rc);
+	rc = encode_pin_entry(argv[2], cmd + 13);
+	if (rc < 0)
+		return(rc);
+	rc = apdu_exchange(cmd, 21);
+	if (rc < 0)
+		return(rc);
+	if (sim_resp_sw != 0x9000) {
+		fprintf(stderr, "bad SW response: %04X\n", sim_resp_sw);
+		return(-1);
+	}
+	return(0);
+}
+
+cmd_disable_chv(argc, argv)
+	char **argv;
+{
+	u_char cmd[13];
+	int rc;
+
+	/* DISABLE CHV command APDU */
+	cmd[0] = 0xA0;
+	cmd[1] = 0x26;
+	cmd[2] = 0x00;
+	cmd[3] = 0x01;
+	cmd[4] = 8;
+	rc = encode_pin_entry(argv[1], cmd + 5);
+	if (rc < 0)
+		return(rc);
+	rc = apdu_exchange(cmd, 13);
+	if (rc < 0)
+		return(rc);
+	if (sim_resp_sw != 0x9000) {
+		fprintf(stderr, "bad SW response: %04X\n", sim_resp_sw);
+		return(-1);
+	}
+	return(0);
+}
+
+cmd_enable_chv(argc, argv)
+	char **argv;
+{
+	u_char cmd[13];
+	int rc;
+
+	/* ENABLE CHV command APDU */
+	cmd[0] = 0xA0;
+	cmd[1] = 0x28;
+	cmd[2] = 0x00;
+	cmd[3] = 0x01;
+	cmd[4] = 8;
+	rc = encode_pin_entry(argv[1], cmd + 5);
+	if (rc < 0)
+		return(rc);
+	rc = apdu_exchange(cmd, 13);
+	if (rc < 0)
+		return(rc);
+	if (sim_resp_sw != 0x9000) {
+		fprintf(stderr, "bad SW response: %04X\n", sim_resp_sw);
+		return(-1);
+	}
+	return(0);
+}
+
+cmd_unblock_chv(argc, argv)
+	char **argv;
+{
+	u_char cmd[21];
+	int rc;
+
+	/* UNBLOCK CHV command APDU */
+	cmd[0] = 0xA0;
+	cmd[1] = 0x2C;
+	cmd[2] = 0x00;
+	switch (argv[0][11]) {
+	case '1':
+		cmd[3] = 0x00;
+		break;
+	case '2':
+		cmd[3] = 0x02;
+		break;
+	default:
+		fprintf(stderr, "BUG in unblock-chvN command\n");
+		return(-1);
+	}
+	cmd[4] = 16;
+	rc = encode_pin_entry(argv[1], cmd + 5);
+	if (rc < 0)
+		return(rc);
+	rc = encode_pin_entry(argv[2], cmd + 13);
+	if (rc < 0)
+		return(rc);
+	rc = apdu_exchange(cmd, 21);
+	if (rc < 0)
+		return(rc);
+	if (sim_resp_sw != 0x9000) {
+		fprintf(stderr, "bad SW response: %04X\n", sim_resp_sw);
+		return(-1);
+	}
+	return(0);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/simtool/curfile.c	Thu Feb 11 23:04:28 2021 +0000
@@ -0,0 +1,4 @@
+/* these global vars hold info about the currently selected EF */
+
+unsigned curfile_total_size, curfile_structure;
+unsigned curfile_record_len, curfile_record_count;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/simtool/curfile.h	Thu Feb 11 23:04:28 2021 +0000
@@ -0,0 +1,4 @@
+/* extern definitions for global vars defined in curfile.c */
+
+extern unsigned curfile_total_size, curfile_structure;
+extern unsigned curfile_record_len, curfile_record_count;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/simtool/dispatch.c	Thu Feb 11 23:04:28 2021 +0000
@@ -0,0 +1,208 @@
+/*
+ * This module implements the command dispatch for fc-simtool
+ */
+
+#include <ctype.h>
+#include <stdio.h>
+#include <string.h>
+#include <strings.h>
+#include <stdlib.h>
+
+extern int cmd_a38();
+extern int cmd_change_chv();
+extern int cmd_disable_chv();
+extern int cmd_enable_chv();
+extern int cmd_exec();
+extern int cmd_fix_sysmo_msisdn();
+extern int cmd_grcard1_set_adm();
+extern int cmd_grcard1_set_ki();
+extern int cmd_grcard1_set_pin();
+extern int cmd_grcard2_set_adm();
+extern int cmd_grcard2_set_pin();
+extern int cmd_grcard2_set_puk();
+extern int cmd_grcard2_set_super();
+extern int cmd_iccid();
+extern int cmd_imsi();
+extern int cmd_pb_dump();
+extern int cmd_pb_dump_rec();
+extern int cmd_pb_erase();
+extern int cmd_pb_erase_one();
+extern int cmd_pb_erase_range();
+extern int cmd_pb_update();
+extern int cmd_pb_update_imm();
+extern int cmd_pb_update_imm_hex();
+extern int cmd_readbin();
+extern int cmd_readef();
+extern int cmd_readrec();
+extern int cmd_restore_file();
+extern int cmd_savebin();
+extern int cmd_save_sms_bin();
+extern int cmd_select();
+extern int cmd_spn();
+extern int cmd_telecom_sum();
+extern int cmd_uicc_dir();
+extern int cmd_unblock_chv();
+extern int cmd_update_bin();
+extern int cmd_update_bin_imm();
+extern int cmd_update_rec();
+extern int cmd_verify_chv();
+extern int cmd_verify_ext();
+
+extern int display_sim_resp_in_hex();
+extern int good_exit();
+
+static struct cmdtab {
+	char *cmd;
+	int minargs;
+	int maxargs;
+	int (*func)();
+} cmdtab[] = {
+	{"a38", 1, 1, cmd_a38},
+	{"change-chv1", 2, 2, cmd_change_chv},
+	{"change-chv2", 2, 2, cmd_change_chv},
+	{"change-pin1", 2, 2, cmd_change_chv},
+	{"change-pin2", 2, 2, cmd_change_chv},
+	{"disable-chv", 1, 1, cmd_disable_chv},
+	{"disable-pin", 1, 1, cmd_disable_chv},
+	{"enable-chv", 1, 1, cmd_enable_chv},
+	{"enable-pin", 1, 1, cmd_enable_chv},
+	{"exec", 1, 1, cmd_exec},
+	{"exit", 0, 0, good_exit},
+	{"fix-sysmo-msisdn", 0, 0, cmd_fix_sysmo_msisdn},
+	{"grcard1-set-adm1", 2, 2, cmd_grcard1_set_adm},
+	{"grcard1-set-adm2", 2, 2, cmd_grcard1_set_adm},
+	{"grcard1-set-ki", 1, 1, cmd_grcard1_set_ki},
+	{"grcard1-set-pin1", 2, 2, cmd_grcard1_set_pin},
+	{"grcard1-set-pin2", 2, 2, cmd_grcard1_set_pin},
+	{"grcard2-set-adm", 1, 1, cmd_grcard2_set_adm},
+	{"grcard2-set-pin1", 1, 1, cmd_grcard2_set_pin},
+	{"grcard2-set-pin2", 1, 1, cmd_grcard2_set_pin},
+	{"grcard2-set-puk1", 1, 1, cmd_grcard2_set_puk},
+	{"grcard2-set-puk2", 1, 1, cmd_grcard2_set_puk},
+	{"grcard2-set-super", 1, 1, cmd_grcard2_set_super},
+	{"iccid", 0, 0, cmd_iccid},
+	{"imsi", 0, 0, cmd_imsi},
+	{"pb-dump", 1, 2, cmd_pb_dump},
+	{"pb-dump-rec", 2, 3, cmd_pb_dump_rec},
+	{"pb-erase", 1, 1, cmd_pb_erase},
+	{"pb-erase-one", 2, 2, cmd_pb_erase_one},
+	{"pb-erase-range", 3, 3, cmd_pb_erase_range},
+	{"pb-update", 2, 2, cmd_pb_update},
+	{"pb-update-imm", 3, 4, cmd_pb_update_imm},
+	{"pb-update-imm-hex", 4, 4, cmd_pb_update_imm_hex},
+	{"quit", 0, 0, good_exit},
+	{"readbin", 2, 2, cmd_readbin},
+	{"readef", 1, 1, cmd_readef},
+	{"readrec", 1, 2, cmd_readrec},
+	{"restore-file", 2, 2, cmd_restore_file},
+	{"savebin", 2, 2, cmd_savebin},
+	{"save-sms-bin", 1, 1, cmd_save_sms_bin},
+	{"select", 1, 1, cmd_select},
+	{"sim-resp", 0, 0, display_sim_resp_in_hex},
+	{"spn", 0, 0, cmd_spn},
+	{"telecom-sum", 0, 0, cmd_telecom_sum},
+	{"uicc-dir", 0, 0, cmd_uicc_dir},
+	{"unblock-chv1", 2, 2, cmd_unblock_chv},
+	{"unblock-chv2", 2, 2, cmd_unblock_chv},
+	{"unblock-pin1", 2, 2, cmd_unblock_chv},
+	{"unblock-pin2", 2, 2, cmd_unblock_chv},
+	{"update-bin", 2, 2, cmd_update_bin},
+	{"update-bin-imm", 2, 2, cmd_update_bin_imm},
+	{"update-rec", 2, 2, cmd_update_rec},
+	{"verify-chv1", 1, 1, cmd_verify_chv},
+	{"verify-chv2", 1, 1, cmd_verify_chv},
+	{"verify-ext", 2, 2, cmd_verify_ext},
+	{"verify-pin1", 1, 1, cmd_verify_chv},
+	{"verify-pin2", 1, 1, cmd_verify_chv},
+	{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/simtool/dumpdir.c	Thu Feb 11 23:04:28 2021 +0000
@@ -0,0 +1,152 @@
+/*
+ * This module implements the dump of EF_DIR.
+ */
+
+#include <sys/types.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include "simresp.h"
+#include "curfile.h"
+#include "file_id.h"
+
+static
+check_all_blank()
+{
+	u_char *dp, *endp;
+
+	dp = sim_resp_data;
+	endp = sim_resp_data + sim_resp_data_len;
+	while (dp < endp)
+		if (*dp++ != 0xFF)
+			return(0);
+	return(1);
+}
+
+static void
+dump_aid(tlv)
+	u_char *tlv;
+{
+	unsigned reclen, n;
+
+	reclen = tlv[1];
+	printf(" AID:");
+	for (n = 0; n < reclen; n++)
+		printf(" %02X", tlv[n+2]);
+	putchar('\n');
+}
+
+static void
+dump_label(tlv)
+	u_char *tlv;
+{
+	int rc;
+	unsigned textlen;
+
+	printf(" Label: ");
+	rc = validate_alpha_field(tlv + 2, tlv[1], &textlen);
+	if (rc < 0) {
+		printf("malformed\n");
+		return;
+	}
+	print_alpha_field(tlv + 2, textlen, stdout);
+	putchar('\n');
+}
+
+static void
+dump_unknown_tlv(tlv)
+	u_char *tlv;
+{
+	unsigned reclen, n;
+
+	reclen = tlv[1] + 2;
+	printf(" TLV:");
+	for (n = 0; n < reclen; n++)
+		printf(" %02X", tlv[n]);
+	putchar('\n');
+}
+
+static void
+dump_record(recno)
+	unsigned recno;
+{
+	unsigned totlen, reclen;
+	u_char *dp, *endp;
+
+	printf("Record #%u:\n", recno);
+	if (sim_resp_data[0] != 0x61) {
+		printf(" bad: first byte != 0x61\n");
+		return;
+	}
+	totlen = sim_resp_data[1];
+	if (totlen < 3 || totlen > 0x7F) {
+		printf(" bad: global length byte 0x%02X is invalid\n", totlen);
+		return;
+	}
+	if (totlen + 2 > sim_resp_data_len) {
+		printf(" bad: TLV global length exceeds EF record length\n");
+		return;
+	}
+	dp = sim_resp_data + 2;
+	endp = sim_resp_data + 2 + totlen;
+	while (dp < endp) {
+		if (endp - dp < 2) {
+trunc_error:		printf(" bad: truncated TLV record\n");
+			return;
+		}
+		if ((dp[0] & 0x1F) == 0x1F) {
+			printf(" bad: extended tag not supported\n");
+			return;
+		}
+		if (dp[1] & 0x80) {
+			printf(" bad: extended length not supported\n");
+			return;
+		}
+		reclen = dp[1] + 2;
+		if (endp - dp < reclen)
+			goto trunc_error;
+		switch (dp[0]) {
+		case 0x4F:
+			dump_aid(dp);
+			break;
+		case 0x50:
+			dump_label(dp);
+			break;
+		default:
+			dump_unknown_tlv(dp);
+		}
+		dp += reclen;
+	}
+}
+
+cmd_uicc_dir()
+{
+	int rc;
+	unsigned recno;
+
+	rc = select_op(FILEID_MF);
+	if (rc < 0)
+		return(rc);
+	rc = select_op(EF_DIR);
+	if (rc < 0)
+		return(rc);
+	rc = parse_ef_select_response();
+	if (rc < 0)
+		return(rc);
+	if (curfile_structure != 0x01) {
+		fprintf(stderr, "error: EF_DIR is not linear fixed\n");
+		return(-1);
+	}
+	if (curfile_record_len < 5) {
+		fprintf(stderr, "error: EF_DIR record length is too short\n");
+		return(-1);
+	}
+	for (recno = 1; recno <= curfile_record_count; recno++) {
+		rc = readrec_op(recno, 0x04, curfile_record_len);
+		if (rc < 0)
+			return(rc);
+		if (check_all_blank())
+			continue;
+		dump_record(recno);
+	}
+	return(0);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/simtool/grcard1.c	Thu Feb 11 23:04:28 2021 +0000
@@ -0,0 +1,115 @@
+/*
+ * This module implements a few special commands for those very few
+ * incredibly lucky people on Earth who have no-longer-available
+ * sysmoSIM-GR1 cards, or any other branded variant of the same card
+ * from Grcard.
+ */
+
+#include <sys/types.h>
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include "simresp.h"
+
+cmd_grcard1_set_pin(argc, argv)
+	char **argv;
+{
+	u_char cmd[21];
+	int rc;
+
+	/* Grcard1 proprietary command APDU */
+	cmd[0] = 0x80;
+	cmd[1] = 0xD4;
+	cmd[2] = 0x00;
+	switch (argv[0][15]) {
+	case '1':
+		cmd[3] = 0x01;
+		break;
+	case '2':
+		cmd[3] = 0x02;
+		break;
+	default:
+		fprintf(stderr, "BUG in grcard1-set-pinN command\n");
+		return(-1);
+	}
+	cmd[4] = 16;
+	rc = encode_pin_entry(argv[1], cmd + 5);
+	if (rc < 0)
+		return(rc);
+	rc = encode_pin_entry(argv[2], cmd + 13);
+	if (rc < 0)
+		return(rc);
+	rc = apdu_exchange(cmd, 21);
+	if (rc < 0)
+		return(rc);
+	if (sim_resp_sw != 0x9000) {
+		fprintf(stderr, "bad SW response: %04X\n", sim_resp_sw);
+		return(-1);
+	}
+	return(0);
+}
+
+cmd_grcard1_set_adm(argc, argv)
+	char **argv;
+{
+	u_char cmd[23];
+	int rc;
+
+	/* Grcard1 proprietary command APDU */
+	cmd[0] = 0x80;
+	cmd[1] = 0xD4;
+	cmd[2] = 0x01;
+	switch (argv[0][15]) {
+	case '1':
+		cmd[3] = 0x04;
+		break;
+	case '2':
+		cmd[3] = 0x05;
+		break;
+	default:
+		fprintf(stderr, "BUG in grcard1-set-admN command\n");
+		return(-1);
+	}
+	cmd[4] = 18;
+	cmd[5] = 0x03;
+	cmd[6] = 0x00;
+	rc = encode_pin_entry(argv[1], cmd + 7);
+	if (rc < 0)
+		return(rc);
+	rc = encode_pin_entry(argv[2], cmd + 15);
+	if (rc < 0)
+		return(rc);
+	rc = apdu_exchange(cmd, 23);
+	if (rc < 0)
+		return(rc);
+	if (sim_resp_sw != 0x9000) {
+		fprintf(stderr, "bad SW response: %04X\n", sim_resp_sw);
+		return(-1);
+	}
+	return(0);
+}
+
+cmd_grcard1_set_ki(argc, argv)
+	char **argv;
+{
+	u_char cmd[21];
+	int rc;
+
+	/* Grcard1 proprietary command APDU */
+	cmd[0] = 0x80;
+	cmd[1] = 0xD4;
+	cmd[2] = 0x02;
+	cmd[3] = 0x00;
+	cmd[4] = 16;
+	rc = decode_hex_data_from_string(argv[1], cmd + 5, 16, 16);
+	if (rc < 0)
+		return(rc);
+	rc = apdu_exchange(cmd, 21);
+	if (rc < 0)
+		return(rc);
+	if (sim_resp_sw != 0x9000) {
+		fprintf(stderr, "bad SW response: %04X\n", sim_resp_sw);
+		return(-1);
+	}
+	return(0);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/simtool/grcard2.c	Thu Feb 11 23:04:28 2021 +0000
@@ -0,0 +1,138 @@
+/*
+ * I, Mother Mychaela, am hoping to get some SIM cards from Grcard
+ * that follow the protocol which the Osmocom community has nicknamed
+ * GrcardSIM2:
+ *
+ * https://osmocom.org/projects/cellular-infrastructure/wiki/GrcardSIM2
+ *
+ * I haven't got these cards yet and may not get them for a long time,
+ * hence the following code has been written blindly, untested.
+ * If anyone in the community happens to have a sysmoSIM-GR2 card
+ * that was once (aeons ago) sold by Sysmocom, please test this code!
+ */
+
+#include <sys/types.h>
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include "simresp.h"
+
+cmd_grcard2_set_pin(argc, argv)
+	char **argv;
+{
+	u_char cmd[13];
+	int rc;
+
+	/* Grcard2 proprietary command APDU */
+	cmd[0] = 0xA0;
+	cmd[1] = 0xD4;
+	cmd[2] = 0x3A;
+	switch (argv[0][15]) {
+	case '1':
+		cmd[3] = 0x01;
+		break;
+	case '2':
+		cmd[3] = 0x02;
+		break;
+	default:
+		fprintf(stderr, "BUG in grcard2-set-pinN command\n");
+		return(-1);
+	}
+	cmd[4] = 8;
+	rc = encode_pin_entry(argv[1], cmd + 5);
+	if (rc < 0)
+		return(rc);
+	rc = apdu_exchange(cmd, 13);
+	if (rc < 0)
+		return(rc);
+	if (sim_resp_sw != 0x9000) {
+		fprintf(stderr, "bad SW response: %04X\n", sim_resp_sw);
+		return(-1);
+	}
+	return(0);
+}
+
+cmd_grcard2_set_puk(argc, argv)
+	char **argv;
+{
+	u_char cmd[13];
+	int rc;
+
+	/* Grcard2 proprietary command APDU */
+	cmd[0] = 0xA0;
+	cmd[1] = 0xD4;
+	cmd[2] = 0x3B;
+	switch (argv[0][15]) {
+	case '1':
+		cmd[3] = 0x00;
+		break;
+	case '2':
+		cmd[3] = 0x02;
+		break;
+	default:
+		fprintf(stderr, "BUG in grcard2-set-pukN command\n");
+		return(-1);
+	}
+	cmd[4] = 8;
+	rc = encode_pin_entry(argv[1], cmd + 5);
+	if (rc < 0)
+		return(rc);
+	rc = apdu_exchange(cmd, 13);
+	if (rc < 0)
+		return(rc);
+	if (sim_resp_sw != 0x9000) {
+		fprintf(stderr, "bad SW response: %04X\n", sim_resp_sw);
+		return(-1);
+	}
+	return(0);
+}
+
+cmd_grcard2_set_adm(argc, argv)
+	char **argv;
+{
+	u_char cmd[13];
+	int rc;
+
+	/* Grcard2 proprietary command APDU */
+	cmd[0] = 0xA0;
+	cmd[1] = 0xD4;
+	cmd[2] = 0x3A;
+	cmd[3] = 0x05;
+	cmd[4] = 8;
+	rc = encode_pin_entry(argv[1], cmd + 5);
+	if (rc < 0)
+		return(rc);
+	rc = apdu_exchange(cmd, 13);
+	if (rc < 0)
+		return(rc);
+	if (sim_resp_sw != 0x9000) {
+		fprintf(stderr, "bad SW response: %04X\n", sim_resp_sw);
+		return(-1);
+	}
+	return(0);
+}
+
+cmd_grcard2_set_super(argc, argv)
+	char **argv;
+{
+	u_char cmd[13];
+	int rc;
+
+	/* Grcard2 proprietary command APDU */
+	cmd[0] = 0xA0;
+	cmd[1] = 0xD4;
+	cmd[2] = 0x3A;
+	cmd[3] = 0x0B;
+	cmd[4] = 8;
+	rc = encode_pin_entry(argv[1], cmd + 5);
+	if (rc < 0)
+		return(rc);
+	rc = apdu_exchange(cmd, 13);
+	if (rc < 0)
+		return(rc);
+	if (sim_resp_sw != 0x9000) {
+		fprintf(stderr, "bad SW response: %04X\n", sim_resp_sw);
+		return(-1);
+	}
+	return(0);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/simtool/hlread.c	Thu Feb 11 23:04:28 2021 +0000
@@ -0,0 +1,125 @@
+/*
+ * This module implements some high-level or user-friendly read commands.
+ */
+
+#include <sys/types.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include "simresp.h"
+#include "curfile.h"
+#include "file_id.h"
+
+encode_hex_digit(d)
+	unsigned d;
+{
+	if (d <= 9)
+		return(d + '0');
+	else
+		return(d - 10 + 'A');
+}
+
+decode_reversed_nibbles(bytes, nbytes, dest)
+	u_char *bytes;
+	unsigned nbytes;
+	char *dest;
+{
+	u_char *sp;
+	char *dp;
+	unsigned n, c;
+
+	sp = bytes;
+	dp = dest;
+	for (n = 0; n < nbytes; n++) {
+		c = *sp & 0xF;
+		*dp++ = encode_hex_digit(c);
+		c = *sp >> 4;
+		*dp++ = encode_hex_digit(c);
+		sp++;
+	}
+}
+
+cmd_iccid()
+{
+	int rc;
+	char buf[21];
+
+	rc = select_op(FILEID_MF);
+	if (rc < 0)
+		return(rc);
+	rc = select_op(EF_ICCID);
+	if (rc < 0)
+		return(rc);
+	rc = parse_ef_select_response();
+	if (rc < 0)
+		return(rc);
+	if (curfile_structure != 0x00 || curfile_total_size != 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);
+}
+
+cmd_imsi()
+{
+	int rc;
+	char buf[17];
+
+	rc = select_op(DF_GSM);
+	if (rc < 0)
+		return(rc);
+	rc = select_op(EF_IMSI);
+	if (rc < 0)
+		return(rc);
+	rc = parse_ef_select_response();
+	if (rc < 0)
+		return(rc);
+	if (curfile_structure != 0x00 || curfile_total_size != 9) {
+		fprintf(stderr, "error: expected transparent EF of 9 bytes\n");
+		return(-1);
+	}
+	rc = readbin_op(0, 9);
+	if (rc < 0)
+		return(rc);
+	decode_reversed_nibbles(sim_resp_data + 1, 8, buf);
+	buf[16] = '\0';
+	printf("%s parity=%c len=%u\n", buf + 1, buf[0], sim_resp_data[0]);
+	return(0);
+}
+
+cmd_spn()
+{
+	int rc;
+	unsigned textlen;
+
+	rc = select_op(DF_GSM);
+	if (rc < 0)
+		return(rc);
+	rc = select_op(EF_SPN);
+	if (rc < 0)
+		return(rc);
+	rc = parse_ef_select_response();
+	if (rc < 0)
+		return(rc);
+	if (curfile_structure != 0x00 || curfile_total_size != 17) {
+		fprintf(stderr, "error: expected transparent EF of 17 bytes\n");
+		return(-1);
+	}
+	rc = readbin_op(0, 17);
+	if (rc < 0)
+		return(rc);
+	printf("Display condition: %02X\n", sim_resp_data[0]);
+	printf("SPN: ");
+	rc = validate_alpha_field(sim_resp_data + 1, 16, &textlen);
+	if (rc >= 0)
+		print_alpha_field(sim_resp_data, textlen, stdout);
+	else
+		printf("malformed alpha field");
+	putchar('\n');
+	return(0);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/simtool/main.c	Thu Feb 11 23:04:28 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("simtool> ", 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/simtool/pbcommon.c	Thu Feb 11 23:04:28 2021 +0000
@@ -0,0 +1,70 @@
+/*
+ * This module implements the common functions for all phonebook commands.
+ */
+
+#include <sys/types.h>
+#include <string.h>
+#include <strings.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include "simresp.h"
+#include "curfile.h"
+#include "file_id.h"
+
+static struct map {
+	char	*user_name;
+	char	*canon_name;
+	int	file_id;
+} phonebook_map[] = {
+	{"adn",		"EF_ADN",	EF_ADN},
+	{"ADN",		"EF_ADN",	EF_ADN},
+	{"EF_ADN",	"EF_ADN",	EF_ADN},
+	{"fdn",		"EF_FDN",	EF_FDN},
+	{"FDN",		"EF_FDN",	EF_FDN},
+	{"EF_FDN",	"EF_FDN",	EF_FDN},
+	{"sdn",		"EF_SDN",	EF_SDN},
+	{"SDN",		"EF_SDN",	EF_SDN},
+	{"EF_SDN",	"EF_SDN",	EF_SDN},
+	{"msisdn",	"EF_MSISDN",	EF_MSISDN},
+	{"MSISDN",	"EF_MSISDN",	EF_MSISDN},
+	{"EF_MSISDN",	"EF_MSISDN",	EF_MSISDN},
+	/* table search terminator */
+	{0,		0,		-1}
+};
+
+phonebook_op_common(reqname)
+	char *reqname;
+{
+	struct map *tp;
+	int rc;
+
+	for (tp = phonebook_map; tp->user_name; tp++)
+		if (!strcmp(tp->user_name, reqname))
+			break;
+	if (!tp->canon_name) {
+		fprintf(stderr, "error: phone book name \"%s\" not known\n",
+			reqname);
+		return(-1);
+	}
+	rc = select_op(DF_TELECOM);
+	if (rc < 0)
+		return(rc);
+	rc = select_op(tp->file_id);
+	if (rc < 0)
+		return(rc);
+	rc = parse_ef_select_response();
+	if (rc < 0)
+		return(rc);
+	if (curfile_structure != 0x01) {
+		fprintf(stderr, "error: %s is not linear fixed\n",
+			tp->canon_name);
+		return(-1);
+	}
+	if (curfile_record_len < 14) {
+		fprintf(stderr,
+	"error: %s has record length of %u bytes, less than minimum 14\n",
+			tp->canon_name, curfile_record_len);
+		return(-1);
+	}
+	return(0);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/simtool/pbdump.c	Thu Feb 11 23:04:28 2021 +0000
@@ -0,0 +1,181 @@
+/*
+ * This module implements the pb-dump command.
+ */
+
+#include <sys/types.h>
+#include <string.h>
+#include <strings.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include "simresp.h"
+#include "curfile.h"
+
+static char gsm_address_digits[16] =
+	{'0','1','2','3','4','5','6','7','8','9','*','#','a','b','c','?'};
+
+static
+check_all_blank()
+{
+	u_char *dp, *endp;
+
+	dp = sim_resp_data;
+	endp = sim_resp_data + sim_resp_data_len;
+	while (dp < endp)
+		if (*dp++ != 0xFF)
+			return(0);
+	return(1);
+}
+
+static
+decode_number(data, nbytes, out)
+	u_char *data;
+	unsigned nbytes;
+	char *out;
+{
+	u_char *dp, *endp;
+	int c;
+
+	dp = data;
+	endp = data + nbytes;
+	while (dp < endp) {
+		c = *dp & 0xF;
+		if (c == 0xF)
+			return(-1);
+		*out++ = gsm_address_digits[c];
+		c = *dp >> 4;
+		if (c == 0xF) {
+			if (dp + 1 == endp)
+				break;
+			else
+				return(-1);
+		}
+		*out++ = gsm_address_digits[c];
+		dp++;
+	}
+	*out = '\0';
+	return(0);
+}
+
+static
+check_blank_area(dp, endp)
+	u_char *dp, *endp;
+{
+	while (dp < endp)
+		if (*dp++ != 0xFF)
+			return(-1);
+	return(0);
+}
+
+static void
+dump_record(recno, outf)
+	unsigned recno;
+	FILE *outf;
+{
+	int rc;
+	unsigned textlen;
+	u_char *fixp;
+	char digits[21];
+
+	fprintf(outf, "#%u: ", recno);
+	if (curfile_record_len > 14) {
+		rc = validate_alpha_field(sim_resp_data,
+					  curfile_record_len - 14,
+					  &textlen);
+		if (rc < 0) {
+malformed:		fprintf(outf, "malformed record\n");
+			return;
+		}
+	} else
+		textlen = 0;
+	fixp = sim_resp_data + sim_resp_data_len - 14;
+	if (fixp[0] < 2 || fixp[0] > 11)
+		goto malformed;
+	rc = decode_number(fixp + 2, fixp[0] - 1, digits);
+	if (rc < 0)
+		goto malformed;
+	rc = check_blank_area(fixp + 1 + fixp[0], fixp + 12);
+	if (rc < 0)
+		goto malformed;
+	/* all checks passed */
+	fprintf(outf, "%s,0x%02X ", digits, fixp[1]);
+	if (fixp[12] != 0xFF)
+		fprintf(outf, "CCP=%u ", fixp[12]);
+	if (fixp[13] != 0xFF)
+		fprintf(outf, "EXT=%u ", fixp[13]);
+	print_alpha_field(sim_resp_data, textlen, outf);
+	putc('\n', outf);
+}
+
+cmd_pb_dump(argc, argv)
+	char **argv;
+{
+	int rc;
+	FILE *outf;
+	unsigned recno;
+
+	rc = phonebook_op_common(argv[1]);
+	if (rc < 0)
+		return(rc);
+	if (argv[2]) {
+		outf = fopen(argv[2], "w");
+		if (!outf) {
+			perror(argv[2]);
+			return(-1);
+		}
+	} else
+		outf = stdout;
+	for (recno = 1; recno <= curfile_record_count; recno++) {
+		rc = readrec_op(recno, 0x04, curfile_record_len);
+		if (rc < 0) {
+			if (argv[2])
+				fclose(outf);
+			return(rc);
+		}
+		if (check_all_blank())
+			continue;
+		dump_record(recno, outf);
+	}
+	if (argv[2])
+		fclose(outf);
+	return(0);
+}
+
+cmd_pb_dump_rec(argc, argv)
+	char **argv;
+{
+	int rc;
+	unsigned recno, startrec, endrec;
+
+	rc = phonebook_op_common(argv[1]);
+	if (rc < 0)
+		return(rc);
+	startrec = strtoul(argv[2], 0, 0);
+	if (startrec < 1 || startrec > curfile_record_count) {
+		fprintf(stderr,
+			"error: specified starting record number is invalid\n");
+		return(-1);
+	}
+	if (argv[3]) {
+		endrec = strtoul(argv[3], 0, 0);
+		if (endrec < 1 || endrec > curfile_record_count) {
+			fprintf(stderr,
+			"error: specified final record number is invalid\n");
+			return(-1);
+		}
+		if (startrec > endrec) {
+			fprintf(stderr,
+				"error: reverse record range specified\n");
+			return(-1);
+		}
+	} else
+		endrec = startrec;
+	for (recno = startrec; recno <= endrec; recno++) {
+		rc = readrec_op(recno, 0x04, curfile_record_len);
+		if (rc < 0)
+			return(rc);
+		if (check_all_blank())
+			continue;
+		dump_record(recno, stdout);
+	}
+	return(0);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/simtool/pberase.c	Thu Feb 11 23:04:28 2021 +0000
@@ -0,0 +1,84 @@
+/*
+ * This module implements the pb-erase command.
+ */
+
+#include <sys/types.h>
+#include <string.h>
+#include <strings.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include "simresp.h"
+#include "curfile.h"
+
+cmd_pb_erase(argc, argv)
+	char **argv;
+{
+	int rc;
+	unsigned recno;
+	u_char record[255];
+
+	rc = phonebook_op_common(argv[1]);
+	if (rc < 0)
+		return(rc);
+	memset(record, 0xFF, curfile_record_len);
+	for (recno = 1; recno <= curfile_record_count; recno++) {
+		rc = update_rec_op(recno, 0x04, record, curfile_record_len);
+		if (rc < 0)
+			return(rc);
+	}
+	return(0);
+}
+
+cmd_pb_erase_one(argc, argv)
+	char **argv;
+{
+	int rc;
+	unsigned recno;
+	u_char record[255];
+
+	rc = phonebook_op_common(argv[1]);
+	if (rc < 0)
+		return(rc);
+	recno = strtoul(argv[2], 0, 0);
+	if (recno < 1 || recno > curfile_record_count) {
+		fprintf(stderr, "error: specified record number is invalid\n");
+		return(-1);
+	}
+	memset(record, 0xFF, curfile_record_len);
+	return update_rec_op(recno, 0x04, record, curfile_record_len);
+}
+
+cmd_pb_erase_range(argc, argv)
+	char **argv;
+{
+	int rc;
+	unsigned recno, startrec, endrec;
+	u_char record[255];
+
+	rc = phonebook_op_common(argv[1]);
+	if (rc < 0)
+		return(rc);
+	startrec = strtoul(argv[2], 0, 0);
+	if (startrec < 1 || startrec > curfile_record_count) {
+		fprintf(stderr,
+			"error: specified starting record number is invalid\n");
+		return(-1);
+	}
+	endrec = strtoul(argv[3], 0, 0);
+	if (endrec < 1 || endrec > curfile_record_count) {
+		fprintf(stderr,
+			"error: specified final record number is invalid\n");
+		return(-1);
+	}
+	if (startrec > endrec) {
+		fprintf(stderr, "error: reverse record range specified\n");
+		return(-1);
+	}
+	memset(record, 0xFF, curfile_record_len);
+	for (recno = startrec; recno <= endrec; recno++) {
+		rc = update_rec_op(recno, 0x04, record, curfile_record_len);
+		if (rc < 0)
+			return(rc);
+	}
+	return(0);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/simtool/pbupdate.c	Thu Feb 11 23:04:28 2021 +0000
@@ -0,0 +1,503 @@
+/*
+ * This module implements the pb-update command.
+ */
+
+#include <sys/types.h>
+#include <ctype.h>
+#include <string.h>
+#include <strings.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include "simresp.h"
+#include "curfile.h"
+
+static u_char gsm7_encode_table[256] = {
+	0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,	/* 0x00 */
+	0xFF, 0xFF, '\n', 0xFF, 0xFF, '\r', 0xFF, 0xFF,
+	0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,	/* 0x10 */
+	0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+	' ',  '!',  '"',  '#',  0x02, '%',  '&',  0x27,	/* 0x20 */
+	'(',  ')',  '*',  '+',  ',',  '-',  '.',  '/',
+	'0',  '1',  '2',  '3',  '4',  '5',  '6',  '7',	/* 0x30 */
+	'8',  '9',  ':',  ';',  '<',  '=',  '>',  '?',
+	0x00, 'A',  'B',  'C',  'D',  'E',  'F',  'G',	/* 0x40 */
+	'H',  'I',  'J',  'K',  'L',  'M',  'N',  'O',
+	'P',  'Q',  'R',  'S',  'T',  'U',  'V',  'W',	/* 0x50 */
+	'X',  'Y',  'Z',  0xBC, 0xAF, 0xBE, 0x94, 0x11,
+	0xFF, 'a',  'b',  'c',  'd',  'e',  'f',  'g',	/* 0x60 */
+	'h',  'i',  'j',  'k',  'l',  'm',  'n',  'o',
+	'p',  'q',  'r',  's',  't',  'u',  'v',  'w',	/* 0x70 */
+	'x',  'y',  'z',  0xA8, 0xC0, 0xA9, 0xBD, 0xFF,
+	0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,	/* 0x80 */
+	0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+	0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,	/* 0x90 */
+	0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+	0xFF, 0x40, 0xFF, 0x01, 0x24, 0x03, 0xFF, 0x5F,	/* 0xA0 */
+	0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+	0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,	/* 0xB0 */
+	0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x60,
+	0xFF, 0xFF, 0xFF, 0xFF, 0x5B, 0x0E, 0x1C, 0x09,	/* 0xC0 */
+	0xFF, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+	0xFF, 0x5D, 0xFF, 0xFF, 0xFF, 0xFF, 0x5C, 0xFF,	/* 0xD0 */
+	0x0B, 0xFF, 0xFF, 0xFF, 0x5E, 0xFF, 0xFF, 0x1E,
+	0x7F, 0xFF, 0xFF, 0xFF, 0x7B, 0x0F, 0x1D, 0xFF,	/* 0xE0 */
+	0x04, 0x05, 0xFF, 0xFF, 0x07, 0xFF, 0xFF, 0xFF,
+	0xFF, 0x7D, 0x08, 0xFF, 0xFF, 0xFF, 0x7C, 0xFF,	/* 0xF0 */
+	0x0C, 0x06, 0xFF, 0xFF, 0x7E, 0xFF, 0xFF, 0xFF
+};
+
+static
+digit_char_to_gsm(ch)
+{
+	switch (ch) {
+	case '0':
+	case '1':
+	case '2':
+	case '3':
+	case '4':
+	case '5':
+	case '6':
+	case '7':
+	case '8':
+	case '9':
+		return (ch - '0');
+	case '*':
+		return 0xA;
+	case '#':
+		return 0xB;
+	case 'a':
+	case 'b':
+	case 'c':
+		return (ch - 'a' + 0xC);
+	case 'A':
+	case 'B':
+	case 'C':
+		return (ch - 'A' + 0xC);
+	}
+	return (-1);
+}
+
+static void
+pack_digit_bytes(digits, dest, num_digit_bytes)
+	u_char *digits, *dest;
+	unsigned num_digit_bytes;
+{
+	u_char *sp, *dp;
+	unsigned n;
+
+	sp = digits;
+	dp = dest;
+	for (n = 0; n < num_digit_bytes; n++) {
+		*dp++ = sp[0] | (sp[1] << 4);
+		sp += 2;
+	}
+}
+
+static char *
+decode_qstring_alpha(cp, record, filename_for_errs, lineno_for_errs)
+	char *cp, *filename_for_errs;
+	u_char *record;
+{
+	unsigned maxlen, acclen, nadd;
+	int c;
+
+	maxlen = curfile_record_len - 14;
+	for (acclen = 0; ; ) {
+		if (*cp == '\0') {
+unterm_qstring:		fprintf(stderr,
+				"%s line %d: unterminated quoted string\n",
+				filename_for_errs, lineno_for_errs);
+			return(0);
+		}
+		if (*cp == '"')
+			break;
+		c = *cp++;
+		if (c == '\\') {
+			if (*cp == '\0')
+				goto unterm_qstring;
+			c = *cp++;
+			switch (c) {
+			case 'n':
+				c = '\n';
+				break;
+			case 'r':
+				c = '\r';
+				break;
+			case '"':
+			case '\\':
+				break;
+			default:
+				fprintf(stderr,
+				"%s line %d: non-understood backslash escape\n",
+					filename_for_errs, lineno_for_errs);
+				return(0);
+			}
+		}
+		c = gsm7_encode_table[c];
+		if (c == 0xFF) {
+			fprintf(stderr,
+	"%s line %d: character in quoted string cannot be encoded in GSM7\n",
+				filename_for_errs, lineno_for_errs);
+			return(0);
+		}
+		if (c & 0x80)
+			nadd = 2;
+		else
+			nadd = 1;
+		if (acclen + nadd > maxlen) {
+			fprintf(stderr,
+		"%s line %d: alpha tag string is longer than SIM limit\n",
+				filename_for_errs, lineno_for_errs);
+			return(0);
+		}
+		if (c & 0x80)
+			record[acclen++] = 0x1B;
+		record[acclen++] = c & 0x7F;
+	}
+	return(cp + 1);
+}
+
+static char *
+decode_hex_alpha(cp, record, filename_for_errs, lineno_for_errs)
+	char *cp, *filename_for_errs;
+	u_char *record;
+{
+	unsigned maxlen, acclen;
+
+	maxlen = curfile_record_len - 14;
+	for (acclen = 0; ; ) {
+		if (!isxdigit(cp[0]) || !isxdigit(cp[1]))
+			break;
+		if (acclen >= maxlen) {
+			fprintf(stderr,
+		"%s line %d: alpha tag string is longer than SIM limit\n",
+				filename_for_errs, lineno_for_errs);
+			return(0);
+		}
+		record[acclen++] = (decode_hex_digit(cp[0]) << 4) |
+				    decode_hex_digit(cp[1]);
+		cp += 2;
+	}
+	return(cp);
+}
+
+static
+process_record(line, filename_for_errs, lineno_for_errs)
+	char *line, *filename_for_errs;
+{
+	unsigned recno;
+	u_char record[255], *fixp;
+	u_char digits[20];
+	unsigned ndigits, num_digit_bytes;
+	char *cp;
+	int c;
+
+	recno = strtoul(line+1, 0, 10);
+	if (recno < 1 || recno > curfile_record_count) {
+		fprintf(stderr, "%s line %d: record number is out of range\n",
+			filename_for_errs, lineno_for_errs);
+		return(-1);
+	}
+	cp = line + 1;
+	while (isdigit(*cp))
+		cp++;
+	if (*cp++ != ':') {
+inv_syntax:	fprintf(stderr, "%s line %d: invalid syntax\n",
+			filename_for_errs, lineno_for_errs);
+		return(-1);
+	}
+	while (isspace(*cp))
+		cp++;
+	memset(record, 0xFF, curfile_record_len);
+	fixp = record + curfile_record_len - 14;
+	if (digit_char_to_gsm(*cp) < 0)
+		goto inv_syntax;
+	for (ndigits = 0; ; ndigits++) {
+		c = digit_char_to_gsm(*cp);
+		if (c < 0)
+			break;
+		cp++;
+		if (ndigits >= 20) {
+			fprintf(stderr, "%s line %d: too many number digits\n",
+				filename_for_errs, lineno_for_errs);
+			return(-1);
+		}
+		digits[ndigits] = c;
+	}
+	if (ndigits & 1)
+		digits[ndigits++] = 0xF;
+	num_digit_bytes = ndigits >> 1;
+	fixp[0] = num_digit_bytes + 1;
+	pack_digit_bytes(digits, fixp + 2, num_digit_bytes);
+	if (*cp++ != ',')
+		goto inv_syntax;
+	if (cp[0] != '0' || cp[1] != 'x' && cp[1] != 'X' || !isxdigit(cp[2]) ||
+	    !isxdigit(cp[3]) || !isspace(cp[4]))
+		goto inv_syntax;
+	fixp[1] = strtoul(cp, 0, 16);
+	cp += 5;
+	while (isspace(*cp))
+		cp++;
+	if (!strncasecmp(cp, "CCP=", 4)) {
+		cp += 4;
+		fixp[12] = strtoul(cp, 0, 0);
+		while (*cp && !isspace(*cp))
+			cp++;
+		while (isspace(*cp))
+			cp++;
+	}
+	if (!strncasecmp(cp, "EXT=", 4)) {
+		cp += 4;
+		fixp[13] = strtoul(cp, 0, 0);
+		while (*cp && !isspace(*cp))
+			cp++;
+		while (isspace(*cp))
+			cp++;
+	}
+	if (*cp == '"') {
+		cp++;
+		cp = decode_qstring_alpha(cp, record, filename_for_errs,
+					  lineno_for_errs);
+		if (!cp)
+			return(-1);
+	} else if (!strncasecmp(cp, "HEX", 3)) {
+		cp += 3;
+		while (isspace(*cp))
+			cp++;
+		cp = decode_hex_alpha(cp, record, filename_for_errs,
+				      lineno_for_errs);
+		if (!cp)
+			return(-1);
+	} else
+		goto inv_syntax;
+	while (isspace(*cp))
+		cp++;
+	if (*cp)
+		goto inv_syntax;
+	return update_rec_op(recno, 0x04, record, curfile_record_len);
+}
+
+cmd_pb_update(argc, argv)
+	char **argv;
+{
+	int rc;
+	FILE *inf;
+	int lineno;
+	char linebuf[1024];
+
+	rc = phonebook_op_common(argv[1]);
+	if (rc < 0)
+		return(rc);
+	inf = fopen(argv[2], "r");
+	if (!inf) {
+		perror(argv[2]);
+		return(-1);
+	}
+	for (lineno = 1; fgets(linebuf, sizeof linebuf, inf); lineno++) {
+		if (!index(linebuf, '\n')) {
+			fprintf(stderr,
+				"%s line %d: too long or missing newline\n",
+				argv[2], lineno);
+			fclose(inf);
+			return(-1);
+		}
+		if (linebuf[0] != '#' || !isdigit(linebuf[1]))
+			continue;
+		rc = process_record(linebuf, argv[2], lineno);
+		if (rc < 0) {
+			fclose(inf);
+			return(rc);
+		}
+	}
+	fclose(inf);
+	return(0);
+}
+
+static
+decode_number_arg(arg, fixp)
+	char *arg;
+	u_char *fixp;
+{
+	u_char digits[20];
+	unsigned ndigits, num_digit_bytes;
+	char *cp, *endp;
+	int c;
+
+	cp = arg;
+	if (*cp == '+') {
+		fixp[1] = 0x91;
+		cp++;
+	} else
+		fixp[1] = 0x81;
+	if (digit_char_to_gsm(*cp) < 0) {
+inv_arg:	fprintf(stderr, "error: invalid phone number argument\n");
+		return(-1);
+	}
+	for (ndigits = 0; ; ndigits++) {
+		c = digit_char_to_gsm(*cp);
+		if (c < 0)
+			break;
+		cp++;
+		if (ndigits >= 20) {
+			fprintf(stderr, "error: too many number digits\n");
+			return(-1);
+		}
+		digits[ndigits] = c;
+	}
+	if (ndigits & 1)
+		digits[ndigits++] = 0xF;
+	num_digit_bytes = ndigits >> 1;
+	fixp[0] = num_digit_bytes + 1;
+	pack_digit_bytes(digits, fixp + 2, num_digit_bytes);
+	if (*cp == ',') {
+		cp++;
+		if (!isdigit(*cp))
+			goto inv_arg;
+		fixp[1] = strtoul(cp, &endp, 0);
+		if (*endp)
+			goto inv_arg;
+	} else if (*cp)
+		goto inv_arg;
+	return(0);
+}
+
+static
+decode_alphatag_arg(arg, record)
+	char *arg;
+	u_char *record;
+{
+	unsigned maxlen, acclen, nadd;
+	char *cp;
+	int c;
+
+	maxlen = curfile_record_len - 14;
+	cp = arg;
+	for (acclen = 0; *cp; ) {
+		c = *cp++;
+		if (c == '\\') {
+			if (*cp == '\0') {
+				fprintf(stderr,
+					"error: dangling backslash escape\n");
+				return(-1);
+			}
+			c = *cp++;
+			switch (c) {
+			case 'n':
+				c = '\n';
+				break;
+			case 'r':
+				c = '\r';
+				break;
+			case '"':
+			case '\\':
+				break;
+			default:
+				fprintf(stderr,
+				"error: non-understood backslash escape\n");
+				return(-1);
+			}
+		}
+		c = gsm7_encode_table[c];
+		if (c == 0xFF) {
+			fprintf(stderr,
+	"error: character in alpha tag string cannot be encoded in GSM7\n");
+			return(-1);
+		}
+		if (c & 0x80)
+			nadd = 2;
+		else
+			nadd = 1;
+		if (acclen + nadd > maxlen) {
+			fprintf(stderr,
+			"error: alpha tag string is longer than SIM limit\n");
+			return(-1);
+		}
+		if (c & 0x80)
+			record[acclen++] = 0x1B;
+		record[acclen++] = c & 0x7F;
+	}
+	return(0);
+}
+
+cmd_pb_update_imm(argc, argv)
+	char **argv;
+{
+	int rc;
+	unsigned recno;
+	u_char record[255], *fixp;
+
+	rc = phonebook_op_common(argv[1]);
+	if (rc < 0)
+		return(rc);
+	recno = strtoul(argv[2], 0, 0);
+	if (recno < 1 || recno > curfile_record_count) {
+		fprintf(stderr, "error: specified record number is invalid\n");
+		return(-1);
+	}
+	memset(record, 0xFF, curfile_record_len);
+	fixp = record + curfile_record_len - 14;
+	rc = decode_number_arg(argv[3], fixp);
+	if (rc < 0)
+		return(rc);
+	if (argv[4]) {
+		rc = decode_alphatag_arg(argv[4], record);
+		if (rc < 0)
+			return(rc);
+	}
+	return update_rec_op(recno, 0x04, record, curfile_record_len);
+}
+
+static
+decode_alphatag_arg_hex(arg, record)
+	char *arg;
+	u_char *record;
+{
+	unsigned maxlen, acclen;
+
+	maxlen = curfile_record_len - 14;
+	for (acclen = 0; ; acclen++) {
+		while (isspace(*arg))
+			arg++;
+		if (!*arg)
+			break;
+		if (!isxdigit(arg[0]) || !isxdigit(arg[1])) {
+			fprintf(stderr, "error: invalid hex string input\n");
+			return(-1);
+		}
+		if (acclen >= maxlen) {
+			fprintf(stderr,
+			"error: alpha tag string is longer than SIM limit\n");
+			return(-1);
+		}
+		record[acclen] = (decode_hex_digit(arg[0]) << 4) |
+				 decode_hex_digit(arg[1]);
+		arg += 2;
+	}
+	return(0);
+}
+
+cmd_pb_update_imm_hex(argc, argv)
+	char **argv;
+{
+	int rc;
+	unsigned recno;
+	u_char record[255], *fixp;
+
+	rc = phonebook_op_common(argv[1]);
+	if (rc < 0)
+		return(rc);
+	recno = strtoul(argv[2], 0, 0);
+	if (recno < 1 || recno > curfile_record_count) {
+		fprintf(stderr, "error: specified record number is invalid\n");
+		return(-1);
+	}
+	memset(record, 0xFF, curfile_record_len);
+	fixp = record + curfile_record_len - 14;
+	rc = decode_number_arg(argv[3], fixp);
+	if (rc < 0)
+		return(rc);
+	rc = decode_alphatag_arg_hex(argv[4], record);
+	if (rc < 0)
+		return(rc);
+	return update_rec_op(recno, 0x04, record, curfile_record_len);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/simtool/readcmd.c	Thu Feb 11 23:04:28 2021 +0000
@@ -0,0 +1,108 @@
+#include <sys/types.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include "simresp.h"
+#include "curfile.h"
+
+cmd_readbin(argc, argv)
+	char **argv;
+{
+	unsigned offset, len;
+	int rc;
+
+	offset = strtoul(argv[1], 0, 0);
+	if (offset > 0xFFFF) {
+		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 (!curfile_record_len) {
+			fprintf(stderr,
+			"error: no current file record length is available\n");
+			return(-1);
+		}
+		len = curfile_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 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 = parse_ef_select_response();
+	if (rc < 0)
+		return(rc);
+	if (curfile_structure != 0x00) {
+		fprintf(stderr,
+			"error: readef command is only for transparent EFs\n");
+		return(-1);
+	}
+	printf("Transparent EF of %u byte(s)\n", curfile_total_size);
+	printf("File status: %02X\n", sim_resp_data[11]);
+	show_access_conditions("UPDATE", sim_resp_data[8] & 0xF);
+	show_access_conditions("READ & SEEK", sim_resp_data[8] >> 4);
+	show_access_conditions("INCREASE", sim_resp_data[9] >> 4);
+	show_access_conditions("INVALIDATE", sim_resp_data[10] & 0xF);
+	show_access_conditions("REHABILITATE", sim_resp_data[10] >> 4);
+	if (!curfile_total_size)
+		return(0);
+	readlen = curfile_total_size;
+	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/simtool/readops.c	Thu Feb 11 23:04:28 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] = 0xA0;
+	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] = 0xA0;
+	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/simtool/saverestore.c	Thu Feb 11 23:04:28 2021 +0000
@@ -0,0 +1,227 @@
+/*
+ * This module implements commands for saving SIM file content in UNIX host
+ * files and restoring these backups back to the SIM.
+ */
+
+#include <sys/types.h>
+#include <sys/file.h>
+#include <sys/stat.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include "simresp.h"
+#include "curfile.h"
+#include "file_id.h"
+
+static
+savebin_transparent(outf)
+	FILE *outf;
+{
+	unsigned off, cc;
+	int rc;
+
+	for (off = 0; off < curfile_total_size; off += cc) {
+		cc = curfile_total_size - off;
+		if (cc > 256)
+			cc = 256;
+		rc = readbin_op(off, cc);
+		if (rc < 0)
+			return(rc);
+		fwrite(sim_resp_data, 1, cc, outf);
+	}
+	return(0);
+}
+
+static
+savebin_records(outf)
+	FILE *outf;
+{
+	unsigned recno;
+	int rc;
+
+	for (recno = 1; recno <= curfile_record_count; recno++) {
+		rc = readrec_op(recno, 0x04, curfile_record_len);
+		if (rc < 0)
+			return(rc);
+		fwrite(sim_resp_data, curfile_record_len, 1, outf);
+	}
+	return(0);
+}
+
+cmd_savebin(argc, argv)
+	char **argv;
+{
+	int file_id, rc;
+	FILE *of;
+
+	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 = parse_ef_select_response();
+	if (rc < 0)
+		return(rc);
+	of = fopen(argv[2], "w");
+	if (!of) {
+		perror(argv[2]);
+		return(-1);
+	}
+	switch (curfile_structure) {
+	case 0x00:
+		/* transparent */
+		rc = savebin_transparent(of);
+		break;
+	case 0x01:
+	case 0x03:
+		/* record-based */
+		rc = savebin_records(of);
+		break;
+	}
+	fclose(of);
+	return(rc);
+}
+
+cmd_save_sms_bin(argc, argv)
+	char **argv;
+{
+	int rc;
+	FILE *of;
+
+	rc = select_op(DF_TELECOM);
+	if (rc < 0)
+		return(rc);
+	rc = select_op(EF_SMS);
+	if (rc < 0)
+		return(rc);
+	rc = parse_ef_select_response();
+	if (rc < 0)
+		return(rc);
+	if (curfile_structure != 0x01 || curfile_record_len != 176) {
+		fprintf(stderr,
+		"error: EF_SMS is not linear fixed with 176-byte records\n");
+		return(-1);
+	}
+	of = fopen(argv[1], "w");
+	if (!of) {
+		perror(argv[1]);
+		return(-1);
+	}
+	rc = savebin_records(of);
+	fclose(of);
+	return(rc);
+}
+
+static
+restore_transparent(data)
+	u_char *data;
+{
+	unsigned off, cc;
+	int rc;
+
+	for (off = 0; off < curfile_total_size; off += cc) {
+		cc = curfile_total_size - off;
+		if (cc > 255)
+			cc = 255;
+		rc = update_bin_op(off, data + off, cc);
+		if (rc < 0)
+			return(rc);
+	}
+	return(0);
+}
+
+static
+restore_records(data)
+	u_char *data;
+{
+	unsigned recno;
+	u_char *dp;
+	int rc;
+
+	dp = data;
+	for (recno = 1; recno <= curfile_record_count; recno++) {
+		rc = update_rec_op(recno, 0x04, dp, curfile_record_len);
+		if (rc < 0)
+			return(rc);
+		dp += curfile_record_len;
+	}
+	return(0);
+}
+
+cmd_restore_file(argc, argv)
+	char **argv;
+{
+	int file_id, rc, fd;
+	struct stat st;
+	u_char *databuf;
+
+	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 = parse_ef_select_response();
+	if (rc < 0)
+		return(rc);
+	if (!curfile_total_size) {
+		printf("SIM indicates file of zero length, nothing to do\n");
+		return(0);
+	}
+	fd = open(argv[2], O_RDONLY);
+	if (fd < 0) {
+		perror(argv[2]);
+		return(-1);
+	}
+	fstat(fd, &st);
+	if ((st.st_mode & S_IFMT) != S_IFREG) {
+		fprintf(stderr, "error: %s is not a regular file\n", argv[2]);
+		close(fd);
+		return(-1);
+	}
+	if (st.st_size != curfile_total_size) {
+		fprintf(stderr,
+	"error: length of %s does not match SIM EF length of %u bytes\n",
+			argv[2], curfile_total_size);
+		close(fd);
+		return(-1);
+	}
+	databuf = malloc(curfile_total_size);
+	if (!databuf) {
+		perror("malloc for file data");
+		close(fd);
+		return(-1);
+	}
+	read(fd, databuf, curfile_total_size);
+	close(fd);
+	switch (curfile_structure) {
+	case 0x00:
+		/* transparent */
+		rc = restore_transparent(databuf);
+		break;
+	case 0x01:
+		/* record-based */
+		rc = restore_records(databuf);
+		break;
+	case 0x03:
+		fprintf(stderr, "error: cyclic files are not supported\n");
+		rc = -1;
+	}
+	free(databuf);
+	return(rc);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/simtool/script.c	Thu Feb 11 23:04:28 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/simtool/select.c	Thu Feb 11 23:04:28 2021 +0000
@@ -0,0 +1,222 @@
+#include <sys/types.h>
+#include <ctype.h>
+#include <string.h>
+#include <strings.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include "simresp.h"
+#include "curfile.h"
+
+select_op(file_id)
+	unsigned file_id;
+{
+	u_char cmd[7];
+	int rc;
+	unsigned expect_resp_len;
+
+	/* SELECT command APDU */
+	cmd[0] = 0xA0;
+	cmd[1] = 0xA4;
+	cmd[2] = 0;
+	cmd[3] = 0;
+	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) != 0x9F00) {
+		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[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);
+}
+
+static void
+show_secret_code_status(name, byte)
+	char *name;
+	unsigned byte;
+{
+	printf("Status of %s: %s, %u attempts left\n", name,
+		byte & 0x80 ? "initialized" : "not initialized",
+		byte & 0x0F);
+}
+
+void
+show_access_conditions(oper_name, cond_code)
+	char *oper_name;
+	unsigned cond_code;
+{
+	static char *cond_names[16] =
+		{"ALW", "CHV1", "CHV2", "RFU3",
+		 "ADM4", "ADM5", "ADM6", "ADM7",
+		 "ADM8", "ADM9", "ADM10", "ADM11",
+		 "ADM12", "ADM13", "ADM14", "NEV"};
+
+	printf("Access condition for %s: %s\n", oper_name,
+		cond_names[cond_code]);
+}
+
+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);
+	if (sim_resp_data_len < 14) {
+		fprintf(stderr,
+	"error: response length of %u bytes is too short for any file type\n",
+			sim_resp_data_len);
+		return(-1);
+	}
+	switch (sim_resp_data[6]) {
+	case 0x01:
+		printf("File type: MF\n");
+		goto mf_or_df;
+	case 0x02:
+		printf("File type: DF\n");
+	mf_or_df:
+		if (sim_resp_data_len < 22) {
+			fprintf(stderr,
+		"error: response length of %u bytes is too short for MF/DF\n",
+				sim_resp_data_len);
+			return(-1);
+		}
+		printf("File characteristics: %02X\n", sim_resp_data[13]);
+		printf("Number of DF children: %u\n", sim_resp_data[14]);
+		printf("Number of EF children: %u\n", sim_resp_data[15]);
+		printf("Number of secret codes: %u\n", sim_resp_data[16]);
+		show_secret_code_status("PIN1", sim_resp_data[18]);
+		show_secret_code_status("PUK1", sim_resp_data[19]);
+		show_secret_code_status("PIN2", sim_resp_data[20]);
+		show_secret_code_status("PUK2", sim_resp_data[21]);
+		break;
+	case 0x04:
+		printf("File type: EF\n");
+		curfile_total_size = (sim_resp_data[2] << 8) | sim_resp_data[3];
+		printf("File size: %u\n", curfile_total_size);
+		curfile_structure = sim_resp_data[13];
+		switch (curfile_structure) {
+		case 0x00:
+			printf("Structure: transparent\n");
+			break;
+		case 0x01:
+			printf("Structure: linear fixed\n");
+			goto ef_record_based;
+		case 0x03:
+			printf("Structure: cyclic\n");
+		ef_record_based:
+			if (sim_resp_data_len < 15) {
+				fprintf(stderr,
+	"error: response length of %u bytes is too short for record-based EF\n",
+					sim_resp_data_len);
+				return(-1);
+			}
+			printf("Record length: %u\n", sim_resp_data[14]);
+			curfile_record_len = sim_resp_data[14];
+			if (curfile_record_len &&
+			    curfile_total_size % curfile_record_len == 0) {
+				curfile_record_count =
+					curfile_total_size / curfile_record_len;
+				printf("Number of records: %u\n",
+					curfile_record_count);
+			} else
+				curfile_record_count = 0;
+			break;
+		default:
+			printf("Structure: %02X (unknown)\n",
+				curfile_structure);
+		}
+		printf("File status: %02X\n", sim_resp_data[11]);
+		show_access_conditions("UPDATE", sim_resp_data[8] & 0xF);
+		show_access_conditions("READ & SEEK", sim_resp_data[8] >> 4);
+		show_access_conditions("INCREASE", sim_resp_data[9] >> 4);
+		show_access_conditions("INVALIDATE", sim_resp_data[10] & 0xF);
+		show_access_conditions("REHABILITATE", sim_resp_data[10] >> 4);
+		break;
+	default:
+		printf("File type: %02X (unknown)\n", sim_resp_data[6]);
+	}
+	return(0);
+}
+
+parse_ef_select_response()
+{
+	if (sim_resp_data_len < 14) {
+		fprintf(stderr,
+		"error: SELECT response length of %u bytes is too short\n",
+			sim_resp_data_len);
+		return(-1);
+	}
+	if (sim_resp_data[6] != 0x04) {
+		fprintf(stderr, "error: selected file is not an EF\n");
+		return(-1);
+	}
+	curfile_total_size = (sim_resp_data[2] << 8) | sim_resp_data[3];
+	curfile_structure = sim_resp_data[13];
+	switch (curfile_structure) {
+	case 0x00:
+		/* transparent */
+		break;
+	case 0x01:
+	case 0x03:
+		/* record-based */
+		if (sim_resp_data_len < 15) {
+			fprintf(stderr,
+"error: SELECT response length of %u bytes is too short for record-based EF\n",
+				sim_resp_data_len);
+			return(-1);
+		}
+		curfile_record_len = sim_resp_data[14];
+		if (!curfile_record_len) {
+			fprintf(stderr,
+		"error: SELECT response indicates record length of 0\n");
+			return(-1);
+		}
+		if (curfile_total_size % curfile_record_len) {
+			fprintf(stderr,
+	"error: returned file size is not divisible by record length\n");
+			return(-1);
+		}
+		curfile_record_count = curfile_total_size / curfile_record_len;
+		break;
+	default:
+		fprintf(stderr, "error: unknown EF structure code %02X\n",
+			curfile_structure);
+		return(-1);
+	}
+	return(0);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/simtool/sysmo.c	Thu Feb 11 23:04:28 2021 +0000
@@ -0,0 +1,64 @@
+/*
+ * This module implements special commands for programmable and
+ * semi-programmable (made-up term for the version without ADM keys)
+ * SIM cards made by Sysmocom.
+ */
+
+#include <sys/types.h>
+#include <string.h>
+#include <strings.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include "simresp.h"
+#include "curfile.h"
+#include "file_id.h"
+
+cmd_fix_sysmo_msisdn()
+{
+	int rc;
+	unsigned n;
+	u_char newrec[34];
+
+	rc = select_op(DF_TELECOM);
+	if (rc < 0)
+		return(rc);
+	rc = select_op(EF_MSISDN);
+	if (rc < 0)
+		return(rc);
+	rc = parse_ef_select_response();
+	if (rc < 0)
+		return(rc);
+	if (curfile_structure != 0x01) {
+		fprintf(stderr, "error: EF_MSISDN is not linear fixed\n");
+		return(-1);
+	}
+	if (curfile_record_len != 34) {
+		fprintf(stderr,
+		"error: expected EF_MSISDN record length of 34 bytes, got %u\n",
+			curfile_record_len);
+		return(-1);
+	}
+	rc = readrec_op(1, 0x04, 34);
+	if (rc < 0)
+		return(rc);
+	for (n = 0; n < 18; n++) {
+		if (sim_resp_data[n] != 0xFF) {
+			fprintf(stderr,
+		"error: non-FF data in the first 18 bytes of alpha tag area\n");
+			return(-1);
+		}
+	}
+	if (sim_resp_data[18] == 0xFF && sim_resp_data[19] == 0xFF) {
+		printf(
+		"last 2 bytes of alpha tag area are clear - already fixed?\n");
+		return(0);
+	}
+	if (sim_resp_data[18] != 0x07 || sim_resp_data[19] != 0x91) {
+		fprintf(stderr,
+	"error: bytes 18 & 19 don't match expected bogus programming\n");
+		return(-1);
+	}
+	memset(newrec, 0xFF, 34);
+	memcpy(newrec + 20, sim_resp_data + 18, 8);
+	return update_rec_op(1, 0x04, newrec, 34);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/simtool/telsum.c	Thu Feb 11 23:04:28 2021 +0000
@@ -0,0 +1,83 @@
+/*
+ * This module implements the telecom-sum (summary info) command.
+ */
+
+#include <sys/types.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include "simresp.h"
+#include "curfile.h"
+#include "file_id.h"
+
+static
+do_phonebook_file(file_id, book_name)
+	unsigned file_id;
+	char *book_name;
+{
+	int rc;
+
+	rc = select_op(file_id);
+	if (rc < 0) {
+		printf("%s not present\n", book_name);
+		return(rc);
+	}
+	rc = parse_ef_select_response();
+	if (rc < 0) {
+		fprintf(stderr, "error occurred on SELECT of EF_%s\n",
+			book_name);
+		return(rc);
+	}
+	if (curfile_structure != 0x01) {
+		fprintf(stderr, "error: EF_%s is not linear fixed\n",
+			book_name);
+		return(-1);
+	}
+	if (curfile_record_len < 14) {
+		fprintf(stderr,
+	"error: EF_%s has record length of %u bytes, less than minimum 14\n",
+			book_name, curfile_record_len);
+		return(-1);
+	}
+	printf("%s has %u entries, %u bytes of alpha tag\n", book_name,
+		curfile_record_count, curfile_record_len - 14);
+	return(0);
+}
+
+static
+do_sms_store()
+{
+	int rc;
+
+	rc = select_op(EF_SMS);
+	if (rc < 0) {
+		printf("EF_SMS not present\n");
+		return(rc);
+	}
+	rc = parse_ef_select_response();
+	if (rc < 0) {
+		fprintf(stderr, "error occurred on SELECT of EF_SMS\n");
+		return(rc);
+	}
+	if (curfile_structure != 0x01 || curfile_record_len != 176) {
+		fprintf(stderr,
+		"error: EF_SMS is not linear fixed with 176-byte records\n");
+		return(-1);
+	}
+	printf("SMS store has %u entries\n", curfile_record_count);
+	return(0);
+}
+
+cmd_telecom_sum()
+{
+	int rc;
+
+	rc = select_op(DF_TELECOM);
+	if (rc < 0)
+		return(rc);
+	do_phonebook_file(EF_ADN, "ADN");
+	do_phonebook_file(EF_FDN, "FDN");
+	do_phonebook_file(EF_SDN, "SDN");
+	do_phonebook_file(EF_MSISDN, "MSISDN");
+	do_sms_store();
+	return(0);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/simtool/writecmd.c	Thu Feb 11 23:04:28 2021 +0000
@@ -0,0 +1,65 @@
+#include <sys/types.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include "curfile.h"
+
+cmd_update_bin(argc, argv)
+	char **argv;
+{
+	unsigned offset, len;
+	u_char data[255];
+	int rc;
+
+	offset = strtoul(argv[1], 0, 0);
+	if (offset > 0xFFFF) {
+		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 > 0xFFFF) {
+		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;
+
+	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 != curfile_record_len) {
+		fprintf(stderr, "error: hex data length != EF record length\n");
+		return(-1);
+	}
+	return update_rec_op(recno, 0x04, data, curfile_record_len);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/simtool/writeops.c	Thu Feb 11 23:04:28 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] = 0xA0;
+	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] = 0xA0;
+	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);
+}