changeset 10:ddd767f6e15b

fc-simtool ported over
author Mychaela Falconia <falcon@freecalypso.org>
date Sun, 14 Mar 2021 07:11:25 +0000
parents c9ef9e91dd8e
children 60fd23186e2e
files .hgignore simtool/Makefile simtool/a38.c simtool/bfsearch.c simtool/chv.c simtool/chvext.c simtool/curfile.c simtool/curfile.h simtool/dispatch.c simtool/dumpdir.c simtool/erasefile.c simtool/fplmn.c simtool/getresp.c simtool/grcard1.c simtool/grcard2.c simtool/hlread.c simtool/inval_rehab.c simtool/lndwrite.c simtool/main.c simtool/miscadm.c simtool/opldump.c simtool/oplprog.c simtool/pbcommon.c simtool/pbdump.c simtool/pberase.c simtool/pbrestore.c simtool/pbupd_imm.c simtool/pbupd_immhex.c simtool/plmnsel.c simtool/pnndump.c simtool/pnnprog.c simtool/readcmd.c simtool/readef.c simtool/readops.c simtool/restorebin.c simtool/savebin.c simtool/script.c simtool/select.c simtool/sjs1_hacks.c simtool/smserase.c simtool/smsp_common.c simtool/smsp_dump.c simtool/smsp_erase.c simtool/smsp_restore.c simtool/smsp_set.c simtool/sstlist.c simtool/sstprog.c simtool/stktest.c simtool/telsum.c simtool/usersum.c simtool/writecmd.c simtool/writeops.c
diffstat 52 files changed, 5980 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- a/.hgignore	Sun Mar 14 06:55:38 2021 +0000
+++ b/.hgignore	Sun Mar 14 07:11:25 2021 +0000
@@ -8,3 +8,5 @@
 ^pcsc/fc-pcsc-atr$
 ^pcsc/fc-pcsc-backend$
 ^pcsc/fc-pcsc-list$
+
+^simtool/fc-simtool$
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/simtool/Makefile	Sun Mar 14 07:11:25 2021 +0000
@@ -0,0 +1,26 @@
+CC=	gcc
+CFLAGS=	-O2
+CPPFLAGS=-I../libcommon
+PROG=	fc-simtool
+OBJS=	a38.o bfsearch.o chv.o chvext.o curfile.o dispatch.o dumpdir.o \
+	erasefile.o fplmn.o getresp.o grcard1.o grcard2.o hlread.o \
+	inval_rehab.o lndwrite.o main.o miscadm.o opldump.o oplprog.o \
+	pbcommon.o pbdump.o pberase.o pbrestore.o pbupd_imm.o pbupd_immhex.o \
+	plmnsel.o pnndump.o pnnprog.o readcmd.o readef.o readops.o restorebin.o\
+	savebin.o script.o select.o sjs1_hacks.o smserase.o smsp_common.o \
+	smsp_dump.o smsp_erase.o smsp_restore.o smsp_set.o sstlist.o sstprog.o \
+	stktest.o telsum.o usersum.o writecmd.o writeops.o
+LIBS=	../libcommon/libcommon.a ../libutil/libutil.a
+INSTBIN=/opt/freecalypso/bin
+
+all:	${PROG}
+
+${PROG}:	${OBJS} ${LIBS}
+	${CC} ${CFLAGS} -o $@ ${OBJS} ${LIBS}
+
+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	Sun Mar 14 07:11:25 2021 +0000
@@ -0,0 +1,61 @@
+/*
+ * 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, outf)
+	char **argv;
+	FILE *outf;
+{
+	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);
+	}
+	fprintf(outf, "SRES: %02X %02X %02X %02X\n", sim_resp_data[0],
+		sim_resp_data[1], sim_resp_data[2], sim_resp_data[3]);
+	fprintf(outf, "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/bfsearch.c	Sun Mar 14 07:11:25 2021 +0000
@@ -0,0 +1,230 @@
+/*
+ * This module implements a brute force search of file ID space at a given
+ * file system directory level.
+ */
+
+#include <sys/types.h>
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include "simresp.h"
+#include "file_id.h"
+
+static
+parse_skip_ids(argv, array, total)
+	char **argv;
+	unsigned *array, total;
+{
+	unsigned n;
+
+	for (n = 0; n < total; n++) {
+		if (!isxdigit(argv[n][0]) || !isxdigit(argv[n][1]) ||
+		    !isxdigit(argv[n][2]) || !isxdigit(argv[n][3]) ||
+		    argv[n][4]) {
+			fprintf(stderr, "error: argument is not 4-digit hex\n");
+			return(-1);
+		}
+		array[n] = strtoul(argv[n], 0, 16);
+	}
+	return(0);
+}
+
+static void
+report_ef_struct(outf)
+	FILE *outf;
+{
+	unsigned total_size, record_len;
+
+	fputs("EF, ", outf);
+	total_size = (sim_resp_data[2] << 8) | sim_resp_data[3];
+	switch (sim_resp_data[13]) {
+	case 0x00:
+		fprintf(outf, "transparent, length %u\n", total_size);
+		return;
+	case 0x01:
+		fputs("linear fixed, ", outf);
+		break;
+	case 0x03:
+		fputs("cyclic, ", outf);
+		break;
+	default:
+		fprintf(outf, "struct 0x%02X\n", sim_resp_data[13]);
+		return;
+	}
+	if (sim_resp_data_len < 15) {
+		fprintf(outf, "response struct cut off\n");
+		return;
+	}
+	record_len = sim_resp_data[14];
+	fprintf(outf, "record length %u", record_len);
+	if (record_len && total_size % record_len == 0)
+		fprintf(outf, ", %u records", total_size / record_len);
+	putc('\n', outf);
+}
+
+cmd_bfsearch(argc, argv, outf)
+	char **argv;
+	FILE *outf;
+{
+	unsigned skip_ids[8], num_skip_ids;
+	unsigned bfs, n;
+	int rc;
+
+	num_skip_ids = argc - 1;
+	rc = parse_skip_ids(argv + 1, skip_ids, num_skip_ids);
+	if (rc < 0)
+		return(rc);
+	rc = elem_select_op(skip_ids[0]);
+	if (rc < 0)
+		return(rc);
+	if (!rc) {
+		fprintf(stderr, "error: starting file ID 0x%04X not found\n",
+			skip_ids[0]);
+		return(-1);
+	}
+	for (bfs = 0; bfs <= 0xFFFF; bfs++) {
+		for (n = 0; n < num_skip_ids; n++) {
+			if (bfs == skip_ids[n])
+				break;
+		}
+		if (n < num_skip_ids)
+			continue;
+		rc = elem_select_op(bfs);
+		if (rc < 0)
+			return(rc);
+		if (!rc)
+			continue;
+		rc = get_response_op();
+		if (rc < 0)
+			return(rc);
+		fprintf(outf, "%04X: ", bfs);
+		if (sim_resp_data_len < 14)
+			fprintf(outf, "too-short response struct\n");
+		else {
+			switch (sim_resp_data[6]) {
+			case 0x01:
+				fprintf(outf, "MF\n");
+				break;
+			case 0x02:
+				fprintf(outf, "DF\n");
+				break;
+			case 0x04:
+				report_ef_struct(outf);
+				break;
+			default:
+				fprintf(outf, "unknown file type %02X\n",
+					sim_resp_data[6]);
+			}
+		}
+		rc = elem_select_op(skip_ids[0]);
+		if (rc < 0)
+			return(rc);
+		if (!rc) {
+			fprintf(stderr,
+			"reselecting starting file ID 0x%04X not-found error\n",
+				skip_ids[0]);
+			return(-1);
+		}
+	}
+	return(0);
+}
+
+static
+bfsearch_dir(path, pathlen, siblings, nsiblings, outf)
+	unsigned *path, pathlen, *siblings, nsiblings;
+	FILE *outf;
+{
+	unsigned bfs, n;
+	unsigned df_children[255], ndfc;
+	unsigned childpath[8];
+	int rc;
+
+	for (n = 0; n < pathlen; n++) {
+		rc = elem_select_op(path[n]);
+		if (rc < 0)
+			return(rc);
+		if (!rc) {
+			fprintf(stderr,
+				"error selecting 0x%04X: file not found\n",
+				path[n]);
+			return(-1);
+		}
+	}
+	ndfc = 0;
+	for (bfs = 0; bfs <= 0xFFFF; bfs++) {
+		for (n = 0; n < pathlen; n++) {
+			if (bfs == path[n])
+				break;
+		}
+		if (n < pathlen)
+			continue;
+		for (n = 0; n < nsiblings; n++) {
+			if (bfs == siblings[n])
+				break;
+		}
+		if (n < nsiblings)
+			continue;
+		rc = elem_select_op(bfs);
+		if (rc < 0)
+			return(rc);
+		if (!rc)
+			continue;
+		rc = get_response_op();
+		if (rc < 0)
+			return(rc);
+		for (n = 0; n < pathlen; n++)
+			fprintf(outf, "%04X/", path[n]);
+		fprintf(outf, "%04X: ", bfs);
+		if (sim_resp_data_len < 14)
+			fprintf(outf, "too-short response struct\n");
+		else {
+			switch (sim_resp_data[6]) {
+			case 0x01:
+				fprintf(outf, "MF\n");
+				break;
+			case 0x02:
+				fprintf(outf, "DF\n");
+				if (ndfc < 255)
+					df_children[ndfc++] = bfs;
+				break;
+			case 0x04:
+				report_ef_struct(outf);
+				break;
+			default:
+				fprintf(outf, "unknown file type %02X\n",
+					sim_resp_data[6]);
+			}
+		}
+		rc = elem_select_op(path[pathlen-1]);
+		if (rc < 0)
+			return(rc);
+		if (!rc) {
+			fprintf(stderr,
+			"reselecting starting file ID 0x%04X not-found error\n",
+				path[pathlen-1]);
+			return(-1);
+		}
+	}
+	if (pathlen >= 8)
+		return(0);
+	for (n = 0; n < pathlen; n++)
+		childpath[n] = path[n];
+	for (n = 0; n < ndfc; n++) {
+		childpath[pathlen] = df_children[n];
+		rc = bfsearch_dir(childpath, pathlen + 1, df_children, ndfc,
+				  outf);
+		if (rc < 0)
+			return(rc);
+	}
+	return(0);
+}
+
+cmd_bfsearch_full(argc, argv, outf)
+	char **argv;
+	FILE *outf;
+{
+	unsigned initpath;
+
+	initpath = FILEID_MF;
+	return bfsearch_dir(&initpath, 1, &initpath, 1, outf);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/simtool/chv.c	Sun Mar 14 07:11:25 2021 +0000
@@ -0,0 +1,219 @@
+/*
+ * This module implements the standard set of CHV commands
+ * for GSM 11.11 SIMs.
+ */
+
+#include <sys/types.h>
+#include <stdio.h>
+#include "simresp.h"
+
+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_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_chv1(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_disable_chv1_rpt(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 && sim_resp_sw != 0x9808) {
+		fprintf(stderr, "bad SW response: %04X\n", sim_resp_sw);
+		return(-1);
+	}
+	return(0);
+}
+
+cmd_enable_chv1(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_enable_chv1_rpt(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 && sim_resp_sw != 0x9808) {
+		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/chvext.c	Sun Mar 14 07:11:25 2021 +0000
@@ -0,0 +1,58 @@
+/*
+ * This module implements some commands for extended (non-standard)
+ * CHV-like operations which some cards use for ADM access control.
+ */
+
+#include <sys/types.h>
+#include <stdio.h>
+#include "simresp.h"
+
+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_verify_hex(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 = decode_hex_data_from_string(argv[2], cmd + 5, 8, 8);
+	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/curfile.c	Sun Mar 14 07:11:25 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	Sun Mar 14 07:11:25 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	Sun Mar 14 07:11:25 2021 +0000
@@ -0,0 +1,395 @@
+/*
+ * 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_apdu();
+extern int cmd_atr();
+extern int cmd_bfsearch();
+extern int cmd_bfsearch_full();
+extern int cmd_cd();
+extern int cmd_change_chv();
+extern int cmd_disable_chv1();
+extern int cmd_disable_chv1_rpt();
+extern int cmd_enable_chv1();
+extern int cmd_enable_chv1_rpt();
+extern int cmd_envelope();
+extern int cmd_envelope_imm();
+extern int cmd_erase_file();
+extern int cmd_exec();
+extern int cmd_exit();
+extern int cmd_fix_sysmo_msisdn();
+extern int cmd_fplmn_dump();
+extern int cmd_fplmn_erase();
+extern int cmd_fplmn_erase_all();
+extern int cmd_fplmn_write();
+extern int cmd_fplmn_write_list();
+extern int cmd_get_response();
+extern int cmd_grcard1_set_adm();
+extern int cmd_grcard1_set_ki();
+extern int cmd_grcard1_set_pin();
+extern int cmd_grcard2_set_adm5();
+extern int cmd_grcard2_set_adm5_hex();
+extern int cmd_grcard2_set_comp128();
+extern int cmd_grcard2_set_ki();
+extern int cmd_grcard2_set_pin();
+extern int cmd_grcard2_set_puk();
+extern int cmd_grcard2_set_super();
+extern int cmd_grcard2_set_super_hex();
+extern int cmd_iccid();
+extern int cmd_imsi();
+extern int cmd_imsi_raw();
+extern int cmd_inval_adn();
+extern int cmd_lnd_dump();
+extern int cmd_lnd_erase();
+extern int cmd_lnd_restore();
+extern int cmd_lnd_write();
+extern int cmd_opl_dump();
+extern int cmd_opl_erase();
+extern int cmd_opl_write();
+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_restore();
+extern int cmd_pb_update();
+extern int cmd_pb_update_imm();
+extern int cmd_pb_update_imm_hex();
+extern int cmd_plmnsel_dump();
+extern int cmd_plmnsel_erase();
+extern int cmd_plmnsel_erase_all();
+extern int cmd_plmnsel_write();
+extern int cmd_plmnsel_write_list();
+extern int cmd_pnn_dump();
+extern int cmd_pnn_erase();
+extern int cmd_pnn_write();
+extern int cmd_readbin();
+extern int cmd_readef();
+extern int cmd_readrec();
+extern int cmd_rehab_adn();
+extern int cmd_rehab_imsi();
+extern int cmd_rehab_loci();
+extern int cmd_restore_file();
+extern int cmd_savebin();
+extern int cmd_save_sms_bin();
+extern int cmd_select();
+extern int cmd_sim_resp();
+extern int cmd_sms_erase_all();
+extern int cmd_sms_erase_one();
+extern int cmd_sms_erase_range();
+extern int cmd_smsp_dump();
+extern int cmd_smsp_erase_all();
+extern int cmd_smsp_erase_one();
+extern int cmd_smsp_erase_range();
+extern int cmd_smsp_restore();
+extern int cmd_smsp_set();
+extern int cmd_smsp_set_tag();
+extern int cmd_spn();
+extern int cmd_sst();
+extern int cmd_telecom_sum();
+extern int cmd_terminal_profile();
+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_update_rec_fill();
+extern int cmd_update_rec_imm();
+extern int cmd_user_sum();
+extern int cmd_verify_chv();
+extern int cmd_verify_ext();
+extern int cmd_verify_hex();
+extern int cmd_verify_sjs1_adm1();
+extern int cmd_write_acc();
+extern int cmd_write_iccid();
+extern int cmd_write_iccid_sh18();
+extern int cmd_write_iccid_sh19();
+extern int cmd_write_imsi();
+extern int cmd_write_imsi_sh();
+extern int cmd_write_spn();
+extern int cmd_write_sst();
+
+extern int current_ef_inval();
+extern int current_ef_rehab();
+
+static struct cmdtab {
+	char *cmd;
+	int minargs;
+	int maxargs;
+	int allow_redir;
+	int (*func)();
+} cmdtab[] = {
+	{"a38", 1, 1, 1, cmd_a38},
+	{"apdu", 1, 1, 0, cmd_apdu},
+	{"atr", 0, 0, 0, cmd_atr},
+	{"bfsearch", 1, 18, 1, cmd_bfsearch},
+	{"bfsearch-full", 0, 0, 1, cmd_bfsearch_full},
+	{"bfsearch-mf", 0, 0, 1, cmd_bfsearch_full},
+	{"cd", 1, 1, 0, cmd_cd},
+	{"change-chv1", 2, 2, 0, cmd_change_chv},
+	{"change-chv2", 2, 2, 0, cmd_change_chv},
+	{"change-pin1", 2, 2, 0, cmd_change_chv},
+	{"change-pin2", 2, 2, 0, cmd_change_chv},
+	{"cur-ef-inval", 0, 0, 0, current_ef_inval},
+	{"cur-ef-rehab", 0, 0, 0, current_ef_rehab},
+	{"disable-chv1", 1, 1, 0, cmd_disable_chv1},
+	{"disable-chv1-rpt", 1, 1, 0, cmd_disable_chv1_rpt},
+	{"disable-pin1", 1, 1, 0, cmd_disable_chv1},
+	{"disable-pin1-rpt", 1, 1, 0, cmd_disable_chv1_rpt},
+	{"enable-chv1", 1, 1, 0, cmd_enable_chv1},
+	{"enable-chv1-rpt", 1, 1, 0, cmd_enable_chv1_rpt},
+	{"enable-pin1", 1, 1, 0, cmd_enable_chv1},
+	{"enable-pin1-rpt", 1, 1, 0, cmd_enable_chv1_rpt},
+	{"envelope", 1, 1, 0, cmd_envelope},
+	{"envelope-imm", 1, 1, 0, cmd_envelope_imm},
+	{"erase-file", 1, 2, 0, cmd_erase_file},
+	{"exec", 1, 1, 0, cmd_exec},
+	{"exit", 0, 1, 0, cmd_exit},
+	{"fix-sysmo-msisdn", 0, 0, 0, cmd_fix_sysmo_msisdn},
+	{"fplmn-dump", 0, 0, 1, cmd_fplmn_dump},
+	{"fplmn-erase", 1, 2, 0, cmd_fplmn_erase},
+	{"fplmn-erase-all", 0, 0, 0, cmd_fplmn_erase_all},
+	{"fplmn-write", 2, 2, 0, cmd_fplmn_write},
+	{"fplmn-write-list", 1, 1, 0, cmd_fplmn_write_list},
+	{"get-response", 1, 1, 1, cmd_get_response},
+	{"grcard1-set-adm1", 2, 2, 0, cmd_grcard1_set_adm},
+	{"grcard1-set-adm2", 2, 2, 0, cmd_grcard1_set_adm},
+	{"grcard1-set-ki", 1, 1, 0, cmd_grcard1_set_ki},
+	{"grcard1-set-pin1", 2, 2, 0, cmd_grcard1_set_pin},
+	{"grcard1-set-pin2", 2, 2, 0, cmd_grcard1_set_pin},
+	{"grcard2-set-adm5", 1, 1, 0, cmd_grcard2_set_adm5},
+	{"grcard2-set-adm5-hex", 1, 1, 0, cmd_grcard2_set_adm5_hex},
+	{"grcard2-set-comp128", 1, 1, 0, cmd_grcard2_set_comp128},
+	{"grcard2-set-ki", 1, 1, 0, cmd_grcard2_set_ki},
+	{"grcard2-set-pin1", 1, 1, 0, cmd_grcard2_set_pin},
+	{"grcard2-set-pin2", 1, 1, 0, cmd_grcard2_set_pin},
+	{"grcard2-set-puk1", 1, 1, 0, cmd_grcard2_set_puk},
+	{"grcard2-set-puk2", 1, 1, 0, cmd_grcard2_set_puk},
+	{"grcard2-set-super", 1, 1, 0, cmd_grcard2_set_super},
+	{"grcard2-set-super-hex", 1, 1, 0, cmd_grcard2_set_super_hex},
+	{"iccid", 0, 0, 1, cmd_iccid},
+	{"imsi", 0, 0, 1, cmd_imsi},
+	{"imsi-raw", 0, 0, 1, cmd_imsi_raw},
+	{"inval-adn", 0, 0, 0, cmd_inval_adn},
+	{"lnd-dump", 0, 0, 1, cmd_lnd_dump},
+	{"lnd-erase", 0, 0, 0, cmd_lnd_erase},
+	{"lnd-restore", 1, 1, 0, cmd_lnd_restore},
+	{"lnd-write", 1, 2, 0, cmd_lnd_write},
+	{"opl-dump", 0, 0, 1, cmd_opl_dump},
+	{"opl-erase", 1, 2, 0, cmd_opl_erase},
+	{"opl-write", 5, 5, 0, cmd_opl_write},
+	{"pb-dump", 1, 1, 1, cmd_pb_dump},
+	{"pb-dump-rec", 2, 3, 1, cmd_pb_dump_rec},
+	{"pb-erase", 1, 1, 0, cmd_pb_erase},
+	{"pb-erase-one", 2, 2, 0, cmd_pb_erase_one},
+	{"pb-erase-range", 3, 3, 0, cmd_pb_erase_range},
+	{"pb-restore", 2, 2, 0, cmd_pb_restore},
+	{"pb-update", 2, 2, 0, cmd_pb_update},
+	{"pb-update-imm", 3, 4, 0, cmd_pb_update_imm},
+	{"pb-update-imm-hex", 4, 4, 0, cmd_pb_update_imm_hex},
+	{"plmnsel-dump", 0, 0, 1, cmd_plmnsel_dump},
+	{"plmnsel-erase", 1, 2, 0, cmd_plmnsel_erase},
+	{"plmnsel-erase-all", 0, 0, 0, cmd_plmnsel_erase_all},
+	{"plmnsel-write", 2, 2, 0, cmd_plmnsel_write},
+	{"plmnsel-write-list", 1, 1, 0, cmd_plmnsel_write_list},
+	{"pnn-dump", 0, 0, 1, cmd_pnn_dump},
+	{"pnn-erase", 1, 2, 0, cmd_pnn_erase},
+	{"pnn-write", 2, 3, 0, cmd_pnn_write},
+	{"quit", 0, 1, 0, cmd_exit},
+	{"readbin", 2, 2, 1, cmd_readbin},
+	{"readef", 1, 1, 1, cmd_readef},
+	{"readrec", 1, 2, 1, cmd_readrec},
+	{"rehab-adn", 0, 0, 0, cmd_rehab_adn},
+	{"rehab-imsi", 0, 0, 0, cmd_rehab_imsi},
+	{"rehab-loci", 0, 0, 0, cmd_rehab_loci},
+	{"restore-file", 2, 2, 0, cmd_restore_file},
+	{"savebin", 2, 2, 0, cmd_savebin},
+	{"save-sms-bin", 1, 1, 0, cmd_save_sms_bin},
+	{"select", 1, 1, 1, cmd_select},
+	{"sim-resp", 0, 0, 1, cmd_sim_resp},
+	{"sms-erase-all", 0, 0, 0, cmd_sms_erase_all},
+	{"sms-erase-one", 1, 1, 0, cmd_sms_erase_one},
+	{"sms-erase-range", 2, 2, 0, cmd_sms_erase_range},
+	{"smsp-dump", 0, 0, 1, cmd_smsp_dump},
+	{"smsp-erase-all", 0, 0, 0, cmd_smsp_erase_all},
+	{"smsp-erase-one", 1, 1, 0, cmd_smsp_erase_one},
+	{"smsp-erase-range", 2, 2, 0, cmd_smsp_erase_range},
+	{"smsp-restore", 1, 1, 0, cmd_smsp_restore},
+	{"smsp-set", 2, 6, 0, cmd_smsp_set},
+	{"smsp-set-tag", 3, 7, 0, cmd_smsp_set_tag},
+	{"spn", 0, 0, 1, cmd_spn},
+	{"sst", 0, 0, 1, cmd_sst},
+	{"telecom-sum", 0, 0, 0, cmd_telecom_sum},
+	{"terminal-profile", 1, 1, 0, cmd_terminal_profile},
+	{"uicc-dir", 0, 0, 1, cmd_uicc_dir},
+	{"unblock-chv1", 2, 2, 0, cmd_unblock_chv},
+	{"unblock-chv2", 2, 2, 0, cmd_unblock_chv},
+	{"unblock-pin1", 2, 2, 0, cmd_unblock_chv},
+	{"unblock-pin2", 2, 2, 0, cmd_unblock_chv},
+	{"update-bin", 2, 2, 0, cmd_update_bin},
+	{"update-bin-imm", 2, 2, 0, cmd_update_bin_imm},
+	{"update-rec", 2, 2, 0, cmd_update_rec},
+	{"update-rec-fill", 2, 2, 0, cmd_update_rec_fill},
+	{"update-rec-imm", 2, 2, 0, cmd_update_rec_imm},
+	{"user-sum", 0, 0, 1, cmd_user_sum},
+	{"verify-chv1", 1, 1, 0, cmd_verify_chv},
+	{"verify-chv2", 1, 1, 0, cmd_verify_chv},
+	{"verify-ext", 2, 2, 0, cmd_verify_ext},
+	{"verify-hex", 2, 2, 0, cmd_verify_hex},
+	{"verify-pin1", 1, 1, 0, cmd_verify_chv},
+	{"verify-pin2", 1, 1, 0, cmd_verify_chv},
+	{"verify-sjs1-adm1", 1, 1, 0, cmd_verify_sjs1_adm1},
+	{"write-acc", 1, 1, 0, cmd_write_acc},
+	{"write-iccid", 1, 1, 0, cmd_write_iccid},
+	{"write-iccid-sh18", 1, 1, 0, cmd_write_iccid_sh18},
+	{"write-iccid-sh19", 1, 1, 0, cmd_write_iccid_sh19},
+	{"write-imsi", 1, 1, 0, cmd_write_imsi},
+	{"write-imsi-sh", 1, 1, 0, cmd_write_imsi_sh},
+	{"write-spn", 2, 2, 0, cmd_write_spn},
+	{"write-sst", 1, 1, 0, cmd_write_sst},
+	{0, 0, 0, 0, 0}
+};
+
+static FILE *
+handle_output_redir(str)
+	char *str;
+{
+	char *cp, *fn;
+	FILE *outf;
+
+	for (cp = str; isspace(*cp); cp++)
+		;
+	if (!*cp || *cp == '#') {
+		fprintf(stderr, "error: no filename after '>'\n");
+		return(0);
+	}
+	for (fn = cp; *cp && !isspace(*cp); cp++)
+		;
+	if (*cp)
+		*cp++ = '\0';
+	while (isspace(*cp))
+		cp++;
+	if (*cp && *cp != '#') {
+		fprintf(stderr, "error: invalid syntax after '>'\n");
+		return(0);
+	}
+	outf = fopen(fn, "w");
+	if (!outf)
+		perror(fn);
+	return outf;
+}
+
+simtool_dispatch_cmd(cmd, is_script)
+	char *cmd;
+{
+	char *argv[20];
+	char *cp, **ap;
+	struct cmdtab *tp;
+	FILE *outf;
+	int rc;
+
+	for (cp = cmd; isspace(*cp); cp++)
+		;
+	if (!*cp || *cp == '#')
+		return(0);
+	if (is_script)
+		printf("Script command: %s\n", cp);
+	if (*cp == '!')
+		return system(cp + 1);
+	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 == '#' || *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;
+	if (*cp == '>') {
+		if (!tp->allow_redir) {
+			fprintf(stderr,
+			"error: command does not support output redirection\n");
+			return(-1);
+		}
+		outf = handle_output_redir(cp + 1);
+		if (!outf)
+			return(-1);
+	} else
+		outf = stdout;
+	rc = tp->func(ap - argv, argv, outf);
+	if (outf != stdout)
+		fclose(outf);
+	return rc;
+}
+
+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, stdout);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/simtool/dumpdir.c	Sun Mar 14 07:11:25 2021 +0000
@@ -0,0 +1,44 @@
+/*
+ * This module implements the dump of EF_DIR.
+ */
+
+#include <sys/types.h>
+#include <stdio.h>
+#include "curfile.h"
+#include "file_id.h"
+
+cmd_uicc_dir(argc, argv, outf)
+	char **argv;
+	FILE *outf;
+{
+	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_simresp_all_blank())
+			continue;
+		fprintf(outf, "Record #%u:\n", recno);
+		dump_efdir_record(outf);
+	}
+	return(0);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/simtool/erasefile.c	Sun Mar 14 07:11:25 2021 +0000
@@ -0,0 +1,109 @@
+/*
+ * This module implements the erase-file command.
+ */
+
+#include <sys/types.h>
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include "curfile.h"
+
+static
+erase_transparent(fill_byte)
+{
+	u_char data[255];
+	unsigned off, cc;
+	int rc;
+
+	memset(data, fill_byte, 255);
+	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, cc);
+		if (rc < 0)
+			return(rc);
+	}
+	return(0);
+}
+
+static
+erase_records(fill_byte)
+{
+	u_char data[255];
+	unsigned recno;
+	int rc;
+
+	memset(data, fill_byte, curfile_record_len);
+	for (recno = 1; recno <= curfile_record_count; recno++) {
+		rc = update_rec_op(recno, 0x04, data, curfile_record_len);
+		if (rc < 0)
+			return(rc);
+	}
+	return(0);
+}
+
+static
+erase_cyclic(fill_byte)
+{
+	u_char data[255];
+	unsigned count;
+	int rc;
+
+	memset(data, fill_byte, curfile_record_len);
+	for (count = 0; count < curfile_record_count; count++) {
+		rc = update_rec_op(0, 0x03, data, curfile_record_len);
+		if (rc < 0)
+			return(rc);
+	}
+	return(0);
+}
+
+cmd_erase_file(argc, argv)
+	char **argv;
+{
+	int file_id, rc;
+	unsigned fill_byte;
+
+	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 (argc > 2) {
+		fill_byte = strtoul(argv[2], 0, 16);
+		if (fill_byte > 0xFF) {
+			fprintf(stderr, "error: invalid fill byte argument\n");
+			return(-1);
+		}
+	} else
+		fill_byte = 0xFF;
+	switch (curfile_structure) {
+	case 0x00:
+		/* transparent */
+		rc = erase_transparent(fill_byte);
+		break;
+	case 0x01:
+		/* record-based */
+		rc = erase_records(fill_byte);
+		break;
+	case 0x03:
+		/* cyclic */
+		rc = erase_cyclic(fill_byte);
+		break;
+	}
+	return(rc);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/simtool/fplmn.c	Sun Mar 14 07:11:25 2021 +0000
@@ -0,0 +1,161 @@
+/*
+ * This module implements commands for working with EF_FPLMN.
+ */
+
+#include <sys/types.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include "simresp.h"
+#include "curfile.h"
+#include "file_id.h"
+
+static
+select_ef_fplmn()
+{
+	int rc;
+
+	rc = select_op(DF_GSM);
+	if (rc < 0)
+		return(rc);
+	rc = select_op(EF_FPLMN);
+	if (rc < 0)
+		return(rc);
+	rc = parse_ef_select_response();
+	if (rc < 0)
+		return(rc);
+	if (curfile_structure != 0x00) {
+		fprintf(stderr, "error: EF_FPLMN is not transparent\n");
+		return(-1);
+	}
+	if (curfile_total_size != 12) {
+		fprintf(stderr,
+			"error: EF_FPLMN size does not equal 12 bytes\n");
+		return(-1);
+	}
+	return(0);
+}
+
+cmd_fplmn_dump(argc, argv, outf)
+	char **argv;
+	FILE *outf;
+{
+	int rc;
+	u_char *dp;
+	char ascbuf[8];
+	unsigned idx;
+
+	rc = select_ef_fplmn();
+	if (rc < 0)
+		return(rc);
+	rc = readbin_op(0, 12);
+	if (rc < 0)
+		return(rc);
+	dp = sim_resp_data;
+	for (idx = 0; idx < 4; idx++, dp += 3) {
+		if (idx)
+			putc(' ', outf);
+		if (dp[0] == 0xFF && dp[1] == 0xFF && dp[2] == 0xFF)
+			fputs("-blank-", outf);
+		else {
+			decode_plmn_3bytes(dp, ascbuf, 1);
+			fputs(ascbuf, outf);
+		}
+	}
+	putc('\n', outf);
+	return(0);
+}
+
+cmd_fplmn_write(argc, argv)
+	char **argv;
+{
+	int rc;
+	unsigned idx;
+	u_char rec[3];
+
+	rc = select_ef_fplmn();
+	if (rc < 0)
+		return(rc);
+	idx = strtoul(argv[1], 0, 0);
+	if (idx >= 4) {
+		fprintf(stderr, "error: specified index is out of range\n");
+		return(-1);
+	}
+	rc = encode_plmn_3bytes(argv[2], rec);
+	if (rc < 0) {
+		fprintf(stderr, "error: invalid MCC-MNC argument\n");
+		return(rc);
+	}
+	return update_bin_op(idx * 3, rec, 3);
+}
+
+cmd_fplmn_write_list(argc, argv)
+	char **argv;
+{
+	int rc;
+	u_char buf[12];
+
+	rc = select_ef_fplmn();
+	if (rc < 0)
+		return(rc);
+	rc = read_plmn_list_from_file(argv[1], buf, 12);
+	if (rc < 0)
+		return(rc);
+	return update_bin_op(0, buf, 12);
+}
+
+cmd_fplmn_erase(argc, argv)
+	char **argv;
+{
+	int rc;
+	unsigned idx, start, end;
+	u_char rec[3];
+
+	rc = select_ef_fplmn();
+	if (rc < 0)
+		return(rc);
+	start = strtoul(argv[1], 0, 0);
+	if (start >= 4) {
+		fprintf(stderr,
+			"error: specified starting index is out of range\n");
+		return(-1);
+	}
+	if (!argv[2])
+		end = start;
+	else if (!strcmp(argv[2], "end"))
+		end = 3;
+	else {
+		end = strtoul(argv[1], 0, 0);
+		if (end >= 4) {
+			fprintf(stderr,
+			"error: specified ending index is out of range\n");
+			return(-1);
+		}
+		if (start > end) {
+			fprintf(stderr,
+				"error: reverse index range specified\n");
+			return(-1);
+		}
+	}
+	memset(rec, 0xFF, 3);
+	for (idx = start; idx <= end; idx++) {
+		rc = update_bin_op(idx * 3, rec, 3);
+		if (rc < 0)
+			return(rc);
+	}
+	return(0);
+}
+
+cmd_fplmn_erase_all(argc, argv)
+	char **argv;
+{
+	int rc;
+	u_char ffbuf[12];
+
+	rc = select_ef_fplmn();
+	if (rc < 0)
+		return(rc);
+	memset(ffbuf, 0xFF, 12);
+	return update_bin_op(0, ffbuf, 12);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/simtool/getresp.c	Sun Mar 14 07:11:25 2021 +0000
@@ -0,0 +1,69 @@
+/*
+ * This module implements an elementary GET RESPONSE command
+ */
+
+#include <sys/types.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include "simresp.h"
+
+cmd_get_response(argc, argv, outf)
+	char **argv;
+	FILE *outf;
+{
+	u_char cmd[5];
+	int rc;
+	unsigned len;
+
+	len = strtoul(argv[1], 0, 0);
+	if (len < 1 || len > 256) {
+		fprintf(stderr, "error: length argument is out of range\n");
+		return(-1);
+	}
+	/* GET RESPONSE command APDU */
+	cmd[0] = 0xA0;
+	cmd[1] = 0xC0;
+	cmd[2] = 0;
+	cmd[3] = 0;
+	cmd[4] = len;
+	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);
+	}
+	display_sim_resp_in_hex(outf);
+	return(0);
+}
+
+get_response_op()
+{
+	u_char cmd[5];
+	int rc;
+	unsigned expect_resp_len;
+
+	expect_resp_len = sim_resp_sw & 0xFF;
+	/* GET RESPONSE command APDU */
+	cmd[0] = 0xA0;
+	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: %04X\n",
+			sim_resp_sw);
+		return(-1);
+	}
+	if (sim_resp_data_len != expect_resp_len) {
+		fprintf(stderr,
+			"error: GET RESPONSE returned %u bytes, expected %u\n",
+			sim_resp_data_len, expect_resp_len);
+		return(-1);
+	}
+	return(0);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/simtool/grcard1.c	Sun Mar 14 07:11:25 2021 +0000
@@ -0,0 +1,113 @@
+/*
+ * 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 <stdio.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	Sun Mar 14 07:11:25 2021 +0000
@@ -0,0 +1,246 @@
+/*
+ * This module implements special commands for the grcard.cn card model
+ * which is known in the Osmocom community as GrcardSIM2:
+ *
+ * https://osmocom.org/projects/cellular-infrastructure/wiki/GrcardSIM2
+ *
+ * The sample cards which Mother Mychaela received from Grcard in 2021-02
+ * are GrcardSIM2, and so are historical sysmoSIM-GR2 and 30C3 cards.
+ */
+
+#include <sys/types.h>
+#include <stdio.h>
+#include "simresp.h"
+#include "curfile.h"
+#include "file_id.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_adm5(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_adm5_hex(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 = decode_hex_data_from_string(argv[1], cmd + 5, 8, 8);
+	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);
+}
+
+cmd_grcard2_set_super_hex(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 = decode_hex_data_from_string(argv[1], cmd + 5, 8, 8);
+	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);
+}
+
+static
+select_ef_weki()
+{
+	int rc;
+
+	rc = select_op(DF_GSM);
+	if (rc < 0)
+		return(rc);
+	rc = select_op(0x0001);		/* proprietary EF */
+	if (rc < 0)
+		return(rc);
+	rc = parse_ef_select_response();
+	if (rc < 0)
+		return(rc);
+	if (curfile_structure != 0x00 || curfile_total_size != 35) {
+		fprintf(stderr,
+			"error: EF_WEKI is not a transparent EF of 35 bytes\n");
+		return(-1);
+	}
+	return(0);
+}
+
+cmd_grcard2_set_comp128(argc, argv)
+	char **argv;
+{
+	int rc;
+	unsigned code;
+	u_char magic_byte;
+
+	if (argv[1][0] < '1' || argv[1][0] > '3' || argv[1][1]) {
+		fprintf(stderr, "error: invalid argument\n");
+		return(-1);
+	}
+	code = argv[1][0] - '1';
+	rc = select_ef_weki();
+	if (rc < 0)
+		return(rc);
+	rc = readbin_op(2, 1);
+	if (rc < 0)
+		return(rc);
+	magic_byte = sim_resp_data[0];
+	magic_byte &= 0xFC;
+	magic_byte |= code;
+	return update_bin_op(2, &magic_byte, 1);
+}
+
+cmd_grcard2_set_ki(argc, argv)
+	char **argv;
+{
+	u_char ki[16];
+	int rc;
+
+	rc = decode_hex_data_from_string(argv[1], ki, 16, 16);
+	if (rc < 0)
+		return(rc);
+	rc = select_ef_weki();
+	if (rc < 0)
+		return(rc);
+	return update_bin_op(3, ki, 16);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/simtool/hlread.c	Sun Mar 14 07:11:25 2021 +0000
@@ -0,0 +1,149 @@
+/*
+ * This module implements some high-level or user-friendly read commands.
+ */
+
+#include <sys/types.h>
+#include <stdio.h>
+#include "simresp.h"
+#include "curfile.h"
+#include "file_id.h"
+
+cmd_iccid(argc, argv, outf)
+	char **argv;
+	FILE *outf;
+{
+	int rc;
+	char buf[21], *cp;
+
+	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);
+	for (cp = buf + 20; (cp > buf + 1) && (cp[-1] == 'F'); cp--)
+		;
+	*cp = '\0';
+	fprintf(outf, "%s\n", buf);
+	return(0);
+}
+
+cmd_imsi(argc, argv, outf)
+	char **argv;
+	FILE *outf;
+{
+	int rc;
+	char buf[17], *endp;
+
+	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);
+	if (sim_resp_data[0] < 1 || sim_resp_data[0] > 8) {
+malformed:	fprintf(stderr, "error: malformed EF_IMSI record\n");
+		return(-1);
+	}
+	decode_reversed_nibbles(sim_resp_data + 1, sim_resp_data[0], buf);
+	endp = buf + (sim_resp_data[0] << 1);
+	switch (buf[0]) {
+	case '1':
+		if (sim_resp_data[0] == 1)
+			goto malformed;
+		*--endp = '\0';
+		break;
+	case '9':
+		*endp = '\0';
+		break;
+	default:
+		goto malformed;
+	}
+	fprintf(outf, "%s\n", buf + 1);
+	return(0);
+}
+
+cmd_imsi_raw(argc, argv, outf)
+	char **argv;
+	FILE *outf;
+{
+	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';
+	fprintf(outf, "%s parity=%c len=%u\n", buf + 1, buf[0],
+		sim_resp_data[0]);
+	return(0);
+}
+
+cmd_spn(argc, argv, outf)
+	char **argv;
+	FILE *outf;
+{
+	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);
+	fprintf(outf, "Display condition: %02X\n", sim_resp_data[0]);
+	fputs("SPN: ", outf);
+	rc = validate_alpha_field(sim_resp_data + 1, 16, &textlen);
+	if (rc >= 0)
+		print_alpha_field(sim_resp_data + 1, textlen, outf);
+	else
+		fputs("malformed alpha field", outf);
+	putc('\n', outf);
+	return(0);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/simtool/inval_rehab.c	Sun Mar 14 07:11:25 2021 +0000
@@ -0,0 +1,105 @@
+/*
+ * This module implements the rarely-used INVALIDATE and REHABILITATE
+ * SIM protocol commands.
+ */
+
+#include <sys/types.h>
+#include <stdio.h>
+#include "simresp.h"
+#include "file_id.h"
+
+current_ef_inval()
+{
+	u_char cmd[5];
+	int rc;
+
+	/* INVALIDATE command APDU */
+	cmd[0] = 0xA0;
+	cmd[1] = 0x04;
+	cmd[2] = 0;
+	cmd[3] = 0;
+	cmd[4] = 0;
+	rc = apdu_exchange(cmd, 5);
+	if (rc < 0)
+		return(rc);
+	if (sim_resp_sw != 0x9000) {
+		fprintf(stderr, "bad SW response to INVALIDATE: %04X\n",
+			sim_resp_sw);
+		return(-1);
+	}
+	return(0);
+}
+
+current_ef_rehab()
+{
+	u_char cmd[5];
+	int rc;
+
+	/* REHABILITATE command APDU */
+	cmd[0] = 0xA0;
+	cmd[1] = 0x44;
+	cmd[2] = 0;
+	cmd[3] = 0;
+	cmd[4] = 0;
+	rc = apdu_exchange(cmd, 5);
+	if (rc < 0)
+		return(rc);
+	if (sim_resp_sw != 0x9000) {
+		fprintf(stderr, "bad SW response to REHABILITATE: %04X\n",
+			sim_resp_sw);
+		return(-1);
+	}
+	return(0);
+}
+
+cmd_inval_adn()
+{
+	int rc;
+
+	rc = select_op(DF_TELECOM);
+	if (rc < 0)
+		return(rc);
+	rc = select_op(EF_ADN);
+	if (rc < 0)
+		return(rc);
+	return current_ef_inval();
+}
+
+cmd_rehab_adn()
+{
+	int rc;
+
+	rc = select_op(DF_TELECOM);
+	if (rc < 0)
+		return(rc);
+	rc = select_op(EF_ADN);
+	if (rc < 0)
+		return(rc);
+	return current_ef_rehab();
+}
+
+cmd_rehab_imsi()
+{
+	int rc;
+
+	rc = select_op(DF_GSM);
+	if (rc < 0)
+		return(rc);
+	rc = select_op(EF_IMSI);
+	if (rc < 0)
+		return(rc);
+	return current_ef_rehab();
+}
+
+cmd_rehab_loci()
+{
+	int rc;
+
+	rc = select_op(DF_GSM);
+	if (rc < 0)
+		return(rc);
+	rc = select_op(EF_LOCI);
+	if (rc < 0)
+		return(rc);
+	return current_ef_rehab();
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/simtool/lndwrite.c	Sun Mar 14 07:11:25 2021 +0000
@@ -0,0 +1,52 @@
+/*
+ * This module implements lnd-write and lnd-erase commands.
+ */
+
+#include <sys/types.h>
+#include <string.h>
+#include <strings.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include "curfile.h"
+
+cmd_lnd_write(argc, argv)
+	char **argv;
+{
+	int rc;
+	u_char record[255], *fixp;
+
+	rc = select_ef_lnd();
+	if (rc < 0)
+		return(rc);
+	memset(record, 0xFF, curfile_record_len);
+	fixp = record + curfile_record_len - 14;
+	rc = encode_phone_number_arg(argv[1], fixp, 0);
+	if (rc < 0)
+		return(rc);
+	if (argv[2]) {
+		rc = qstring_arg_to_gsm7(argv[2], record,
+					 curfile_record_len - 14);
+		if (rc < 0)
+			return(rc);
+	}
+	return update_rec_op(0, 0x03, record, curfile_record_len);
+}
+
+cmd_lnd_erase(argc, argv)
+	char **argv;
+{
+	int rc;
+	u_char record[255];
+	unsigned count;
+
+	rc = select_ef_lnd();
+	if (rc < 0)
+		return(rc);
+	memset(record, 0xFF, curfile_record_len);
+	for (count = 0; count < curfile_record_count; count++) {
+		rc = update_rec_op(0, 0x03, record, curfile_record_len);
+		if (rc < 0)
+			return(rc);
+	}
+	return(0);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/simtool/main.c	Sun Mar 14 07:11:25 2021 +0000
@@ -0,0 +1,38 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+extern char be_reader_name[];
+extern char be_atr_string[];
+
+main(argc, argv)
+	char **argv;
+{
+	extern int optind;
+	char command[512];
+	int rc;
+
+	parse_global_options(argc, argv);
+	launch_backend();
+	collect_backend_init_strings();
+	if (argc > optind) {
+		rc = dispatch_ready_argv(argc - optind, argv + optind);
+		if (rc)
+			exit(1);
+		else
+			good_exit();
+	}
+	if (be_reader_name)
+		printf("Card reader name: %s\n", be_reader_name);
+	if (be_atr_string)
+		printf("ATR: %s\n", be_atr_string);
+	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/miscadm.c	Sun Mar 14 07:11:25 2021 +0000
@@ -0,0 +1,216 @@
+/*
+ * This module implements write-iccid and write-imsi commands,
+ * available only in the admin programming phase after authenticating
+ * with some card-vendor-dependent ADM key.
+ */
+
+#include <sys/types.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include "curfile.h"
+#include "file_id.h"
+
+static
+write_iccid_bin(binrec)
+	u_char *binrec;
+{
+	int rc;
+
+	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: EF_ICCID is not a transparent EF of 10 bytes\n");
+		return(-1);
+	}
+	return update_bin_op(0, binrec, 10);
+}
+
+cmd_write_iccid(argc, argv)
+	char **argv;
+{
+	int rc;
+	u_char nibbles[20], binrec[10];
+
+	rc = parse_decimal_string_arg(argv[1], nibbles, 20);
+	if (rc < 0)
+		return(rc);
+	pack_reversed_nibbles(nibbles, binrec, 10);
+	return write_iccid_bin(binrec);
+}
+
+cmd_write_iccid_sh18(argc, argv)
+	char **argv;
+{
+	int rc;
+	u_char nibbles[20], binrec[10];
+
+	rc = parse_decimal_shorthand(argv[1], nibbles, 18);
+	if (rc < 0)
+		return(rc);
+	nibbles[18] = compute_iccid_luhn(nibbles);
+	nibbles[19] = 0xF;
+	pack_reversed_nibbles(nibbles, binrec, 10);
+	return write_iccid_bin(binrec);
+}
+
+cmd_write_iccid_sh19(argc, argv)
+	char **argv;
+{
+	int rc;
+	u_char nibbles[20], binrec[10];
+
+	rc = parse_decimal_shorthand(argv[1], nibbles, 19);
+	if (rc < 0)
+		return(rc);
+	if (nibbles[18] != compute_iccid_luhn(nibbles)) {
+		fprintf(stderr, "error: Luhn check digit mismatch\n");
+		return(-1);
+	}
+	nibbles[19] = 0xF;
+	pack_reversed_nibbles(nibbles, binrec, 10);
+	return write_iccid_bin(binrec);
+}
+
+static
+write_imsi_bin(binrec)
+	u_char *binrec;
+{
+	int rc;
+
+	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: EF_IMSI is not a transparent EF of 9 bytes\n");
+		return(-1);
+	}
+	return update_bin_op(0, binrec, 9);
+}
+
+cmd_write_imsi(argc, argv)
+	char **argv;
+{
+	int rc;
+	u_char nibbles[16], binrec[9];
+	unsigned ndig;
+
+	rc = parse_decimal_string_arg(argv[1], nibbles + 1, 15);
+	if (rc < 0)
+		return(rc);
+	ndig = rc;
+	if (ndig & 1)
+		nibbles[0] = 9;
+	else
+		nibbles[0] = 1;
+	binrec[0] = (ndig + 2) >> 1;
+	pack_reversed_nibbles(nibbles, binrec + 1, 8);
+	return write_imsi_bin(binrec);
+}
+
+cmd_write_imsi_sh(argc, argv)
+	char **argv;
+{
+	int rc;
+	u_char nibbles[16], binrec[9];
+
+	rc = parse_decimal_shorthand(argv[1], nibbles + 1, 15);
+	if (rc < 0)
+		return(rc);
+	nibbles[0] = 9;
+	binrec[0] = 8;
+	pack_reversed_nibbles(nibbles, binrec + 1, 8);
+	return write_imsi_bin(binrec);
+}
+
+static
+write_acc_bin(binrec)
+	u_char *binrec;
+{
+	int rc;
+
+	rc = select_op(DF_GSM);
+	if (rc < 0)
+		return(rc);
+	rc = select_op(EF_ACC);
+	if (rc < 0)
+		return(rc);
+	rc = parse_ef_select_response();
+	if (rc < 0)
+		return(rc);
+	if (curfile_structure != 0x00 || curfile_total_size != 2) {
+		fprintf(stderr,
+			"error: EF_ACC is not a transparent EF of 2 bytes\n");
+		return(-1);
+	}
+	return update_bin_op(0, binrec, 2);
+}
+
+cmd_write_acc(argc, argv)
+	char **argv;
+{
+	unsigned acc;
+	u_char binrec[2];
+
+	acc = strtoul(argv[1], 0, 16);
+	if (acc > 0xFFFF) {
+		fprintf(stderr, "error: ACC argument is out of range\n");
+		return(-1);
+	}
+	binrec[0] = acc >> 8;
+	binrec[1] = acc;
+	return write_acc_bin(binrec);
+}
+
+static
+write_spn_bin(binrec)
+	u_char *binrec;
+{
+	int rc;
+
+	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: EF_SPN is not a transparent EF of 17 bytes\n");
+		return(-1);
+	}
+	return update_bin_op(0, binrec, 17);
+}
+
+cmd_write_spn(argc, argv)
+	char **argv;
+{
+	u_char binrec[17];
+	int rc;
+
+	binrec[0] = strtoul(argv[1], 0, 16);
+	memset(binrec + 1, 0xFF, 16);
+	rc = qstring_arg_to_gsm7(argv[2], binrec + 1, 16);
+	if (rc < 0)
+		return(rc);
+	return write_spn_bin(binrec);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/simtool/opldump.c	Sun Mar 14 07:11:25 2021 +0000
@@ -0,0 +1,69 @@
+/*
+ * This module implements the opl-dump command,
+ * a companion command to pnn-dump.
+ */
+
+#include <sys/types.h>
+#include <stdio.h>
+#include "simresp.h"
+#include "curfile.h"
+#include "file_id.h"
+
+select_ef_opl()
+{
+	int rc;
+
+	rc = select_op(DF_GSM);
+	if (rc < 0)
+		return(rc);
+	rc = select_op(EF_OPL);
+	if (rc < 0)
+		return(rc);
+	rc = parse_ef_select_response();
+	if (rc < 0)
+		return(rc);
+	if (curfile_structure != 0x01) {
+		fprintf(stderr, "error: EF_OPL is not linear fixed\n");
+		return(-1);
+	}
+	if (curfile_record_len < 8) {
+		fprintf(stderr,
+"error: EF_OPL record length is less than the spec minimum of 8 bytes\n");
+		return(-1);
+	}
+	return(0);
+}
+
+static void
+dump_record(recno, outf)
+	unsigned recno;
+	FILE *outf;
+{
+	char ascbuf[8];
+
+	decode_plmn_3bytes(sim_resp_data, ascbuf, 0);
+	fprintf(outf, "#%u: %s %02X%02X-%02X%02X %u\n", recno, ascbuf,
+		sim_resp_data[3], sim_resp_data[4], sim_resp_data[5],
+		sim_resp_data[6], sim_resp_data[7]);
+}
+
+cmd_opl_dump(argc, argv, outf)
+	char **argv;
+	FILE *outf;
+{
+	int rc;
+	unsigned recno;
+
+	rc = select_ef_opl();
+	if (rc < 0)
+		return(rc);
+	for (recno = 1; recno <= curfile_record_count; recno++) {
+		rc = readrec_op(recno, 0x04, curfile_record_len);
+		if (rc < 0)
+			return(rc);
+		if (check_simresp_all_blank())
+			continue;
+		dump_record(recno, outf);
+	}
+	return(0);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/simtool/oplprog.c	Sun Mar 14 07:11:25 2021 +0000
@@ -0,0 +1,84 @@
+/*
+ * This module implements functions for admin programming of EF_OPL.
+ */
+
+#include <sys/types.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include "curfile.h"
+
+cmd_opl_write(argc, argv)
+	char **argv;
+{
+	int rc;
+	unsigned recno, lac;
+	u_char record[255];
+
+	rc = select_ef_opl();
+	if (rc < 0)
+		return(rc);
+	recno = strtoul(argv[1], 0, 0);
+	if (recno < 1 || recno > curfile_record_count) {
+		fprintf(stderr, "error: specified record number is invalid\n");
+		return(-1);
+	}
+	rc = encode_plmn_3bytes(argv[2], record);
+	if (rc < 0) {
+		fprintf(stderr, "error: invalid MCC-MNC argument\n");
+		return(rc);
+	}
+	lac = strtoul(argv[3], 0, 16);
+	record[3] = lac >> 8;
+	record[4] = lac;
+	lac = strtoul(argv[4], 0, 16);
+	record[5] = lac >> 8;
+	record[6] = lac;
+	record[7] = strtoul(argv[5], 0, 0);
+	if (curfile_record_len > 8)
+		memset(record + 8, 0xFF, curfile_record_len - 8);
+	return update_rec_op(recno, 0x04, record, curfile_record_len);
+}
+
+cmd_opl_erase(argc, argv)
+	char **argv;
+{
+	int rc;
+	unsigned recno, startrec, endrec;
+	u_char record[255];
+
+	rc = select_ef_opl();
+	if (rc < 0)
+		return(rc);
+	startrec = strtoul(argv[1], 0, 0);
+	if (startrec < 1 || startrec > curfile_record_count) {
+		fprintf(stderr,
+			"error: specified starting record number is invalid\n");
+		return(-1);
+	}
+	if (!argv[2])
+		endrec = startrec;
+	else if (!strcmp(argv[2], "end"))
+		endrec = curfile_record_count;
+	else {
+		endrec = strtoul(argv[2], 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/pbcommon.c	Sun Mar 14 07:11:25 2021 +0000
@@ -0,0 +1,100 @@
+/*
+ * 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	dir_id;
+	int	file_id;
+} phonebook_map[] = {
+	{"adn",		"EF_ADN",	DF_TELECOM,	EF_ADN},
+	{"ADN",		"EF_ADN",	DF_TELECOM,	EF_ADN},
+	{"EF_ADN",	"EF_ADN",	DF_TELECOM,	EF_ADN},
+	{"fdn",		"EF_FDN",	DF_TELECOM,	EF_FDN},
+	{"FDN",		"EF_FDN",	DF_TELECOM,	EF_FDN},
+	{"EF_FDN",	"EF_FDN",	DF_TELECOM,	EF_FDN},
+	{"sdn",		"EF_SDN",	DF_TELECOM,	EF_SDN},
+	{"SDN",		"EF_SDN",	DF_TELECOM,	EF_SDN},
+	{"EF_SDN",	"EF_SDN",	DF_TELECOM,	EF_SDN},
+	{"msisdn",	"EF_MSISDN",	DF_TELECOM,	EF_MSISDN},
+	{"MSISDN",	"EF_MSISDN",	DF_TELECOM,	EF_MSISDN},
+	{"EF_MSISDN",	"EF_MSISDN",	DF_TELECOM,	EF_MSISDN},
+	{"mbdn",	"EF_MBDN",	DF_GSM,		EF_MBDN},
+	{"MBDN",	"EF_MBDN",	DF_GSM,		EF_MBDN},
+	{"EF_MBDN",	"EF_MBDN",	DF_GSM,		EF_MBDN},
+	/* table search terminator */
+	{0,		0,		-1,		-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(tp->dir_id);
+	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);
+}
+
+select_ef_lnd()
+{
+	int rc;
+
+	rc = select_op(DF_TELECOM);
+	if (rc < 0)
+		return(rc);
+	rc = select_op(EF_LND);
+	if (rc < 0)
+		return(rc);
+	rc = parse_ef_select_response();
+	if (rc < 0)
+		return(rc);
+	if (curfile_structure != 0x03) {
+		fprintf(stderr, "error: EF_LND is not cyclic\n");
+		return(-1);
+	}
+	if (curfile_record_len < 14) {
+		fprintf(stderr,
+	"error: EF_LND has record length of %u bytes, less than minimum 14\n",
+			curfile_record_len);
+		return(-1);
+	}
+	return(0);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/simtool/pbdump.c	Sun Mar 14 07:11:25 2021 +0000
@@ -0,0 +1,142 @@
+/*
+ * This module implements pb-dump and pb-dump-rec commands.
+ */
+
+#include <sys/types.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include "simresp.h"
+#include "curfile.h"
+
+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 (sim_resp_data_len > 14) {
+		rc = validate_alpha_field(sim_resp_data,
+					  sim_resp_data_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_phone_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, outf)
+	char **argv;
+	FILE *outf;
+{
+	int rc;
+	unsigned recno;
+
+	rc = phonebook_op_common(argv[1]);
+	if (rc < 0)
+		return(rc);
+	for (recno = 1; recno <= curfile_record_count; recno++) {
+		rc = readrec_op(recno, 0x04, curfile_record_len);
+		if (rc < 0)
+			return(rc);
+		if (check_simresp_all_blank())
+			continue;
+		dump_record(recno, outf);
+	}
+	return(0);
+}
+
+cmd_pb_dump_rec(argc, argv, outf)
+	char **argv;
+	FILE *outf;
+{
+	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_simresp_all_blank())
+			continue;
+		dump_record(recno, outf);
+	}
+	return(0);
+}
+
+cmd_lnd_dump(argc, argv, outf)
+	char **argv;
+	FILE *outf;
+{
+	int rc;
+	unsigned recno;
+
+	rc = select_ef_lnd();
+	if (rc < 0)
+		return(rc);
+	for (recno = 1; recno <= curfile_record_count; recno++) {
+		rc = readrec_op(recno, 0x04, curfile_record_len);
+		if (rc < 0)
+			return(rc);
+		if (check_simresp_all_blank())
+			continue;
+		dump_record(recno, outf);
+	}
+	return(0);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/simtool/pberase.c	Sun Mar 14 07:11:25 2021 +0000
@@ -0,0 +1,89 @@
+/*
+ * This module implements the pb-erase family of commands.
+ */
+
+#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);
+	}
+	if (!strcmp(argv[3], "end"))
+		endrec = curfile_record_count;
+	else {
+		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/pbrestore.c	Sun Mar 14 07:11:25 2021 +0000
@@ -0,0 +1,252 @@
+/*
+ * This module implements pb-restore and pb-update commands.
+ */
+
+#include <sys/types.h>
+#include <ctype.h>
+#include <string.h>
+#include <strings.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include "curfile.h"
+
+extern FILE *open_script_input_file();
+
+extern char *alpha_from_file_qstring();
+extern char *alpha_from_file_hex();
+
+static
+process_record(line, bin_file_buf, filename_for_errs, lineno_for_errs)
+	char *line, *filename_for_errs;
+	u_char *bin_file_buf;
+{
+	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 = alpha_from_file_qstring(cp, record,
+					     curfile_record_len - 14,
+					     filename_for_errs,
+					     lineno_for_errs);
+		if (!cp)
+			return(-1);
+	} else if (!strncasecmp(cp, "HEX", 3)) {
+		cp += 3;
+		while (isspace(*cp))
+			cp++;
+		cp = alpha_from_file_hex(cp, record, curfile_record_len - 14,
+					 filename_for_errs, lineno_for_errs);
+		if (!cp)
+			return(-1);
+	} else
+		goto inv_syntax;
+	while (isspace(*cp))
+		cp++;
+	if (*cp)
+		goto inv_syntax;
+	if (bin_file_buf) {
+		bcopy(record, bin_file_buf + (recno - 1) * curfile_record_len,
+			curfile_record_len);
+		return(0);
+	} else
+		return update_rec_op(recno, 0x04, record, curfile_record_len);
+}
+
+cmd_pb_restore(argc, argv)
+	char **argv;
+{
+	int rc;
+	FILE *inf;
+	int lineno;
+	char linebuf[1024];
+	u_char *databuf;
+
+	rc = phonebook_op_common(argv[1]);
+	if (rc < 0)
+		return(rc);
+	databuf = malloc(curfile_total_size);
+	if (!databuf) {
+		perror("malloc for full phonebook EF");
+		return(-1);
+	}
+	inf = open_script_input_file(argv[2]);
+	if (!inf) {
+		perror(argv[2]);
+		free(databuf);
+		return(-1);
+	}
+	memset(databuf, 0xFF, curfile_total_size);
+	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);
+			free(databuf);
+			return(-1);
+		}
+		if (linebuf[0] != '#' || !isdigit(linebuf[1]))
+			continue;
+		rc = process_record(linebuf, databuf, argv[2], lineno);
+		if (rc < 0) {
+			fclose(inf);
+			free(databuf);
+			return(rc);
+		}
+	}
+	fclose(inf);
+	rc = restore_bin_records(databuf);
+	free(databuf);
+	return(rc);
+}
+
+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 = open_script_input_file(argv[2]);
+	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, 0, argv[2], lineno);
+		if (rc < 0) {
+			fclose(inf);
+			return(rc);
+		}
+	}
+	fclose(inf);
+	return(0);
+}
+
+cmd_lnd_restore(argc, argv)
+	char **argv;
+{
+	int rc;
+	FILE *inf;
+	int lineno;
+	char linebuf[1024];
+	u_char *databuf;
+
+	rc = select_ef_lnd();
+	if (rc < 0)
+		return(rc);
+	databuf = malloc(curfile_total_size);
+	if (!databuf) {
+		perror("malloc for full EF_LND");
+		return(-1);
+	}
+	inf = open_script_input_file(argv[1]);
+	if (!inf) {
+		perror(argv[1]);
+		free(databuf);
+		return(-1);
+	}
+	memset(databuf, 0xFF, curfile_total_size);
+	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[1], lineno);
+			fclose(inf);
+			free(databuf);
+			return(-1);
+		}
+		if (linebuf[0] != '#' || !isdigit(linebuf[1]))
+			continue;
+		rc = process_record(linebuf, databuf, argv[1], lineno);
+		if (rc < 0) {
+			fclose(inf);
+			free(databuf);
+			return(rc);
+		}
+	}
+	fclose(inf);
+	rc = restore_bin_cyclic(databuf);
+	free(databuf);
+	return(rc);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/simtool/pbupd_imm.c	Sun Mar 14 07:11:25 2021 +0000
@@ -0,0 +1,39 @@
+/*
+ * This module implements the pb-update-imm command.
+ */
+
+#include <sys/types.h>
+#include <string.h>
+#include <strings.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include "curfile.h"
+
+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 = encode_phone_number_arg(argv[3], fixp, 0);
+	if (rc < 0)
+		return(rc);
+	if (argv[4]) {
+		rc = qstring_arg_to_gsm7(argv[4], record,
+					 curfile_record_len - 14);
+		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/pbupd_immhex.c	Sun Mar 14 07:11:25 2021 +0000
@@ -0,0 +1,66 @@
+/*
+ * This module implements the pb-update-imm-hex command.
+ */
+
+#include <sys/types.h>
+#include <ctype.h>
+#include <string.h>
+#include <strings.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include "curfile.h"
+
+static
+decode_alphatag_arg_hex(arg, record, maxlen)
+	char *arg;
+	u_char *record;
+	unsigned maxlen;
+{
+	unsigned acclen;
+
+	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 = encode_phone_number_arg(argv[3], fixp, 0);
+	if (rc < 0)
+		return(rc);
+	rc = decode_alphatag_arg_hex(argv[4], record, curfile_record_len - 14);
+	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/plmnsel.c	Sun Mar 14 07:11:25 2021 +0000
@@ -0,0 +1,189 @@
+/*
+ * This module implements commands for working with EF_PLMNsel.
+ */
+
+#include <sys/types.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include "simresp.h"
+#include "curfile.h"
+#include "file_id.h"
+
+static
+select_ef_plmnsel()
+{
+	int rc;
+
+	rc = select_op(DF_GSM);
+	if (rc < 0)
+		return(rc);
+	rc = select_op(EF_PLMNsel);
+	if (rc < 0)
+		return(rc);
+	rc = parse_ef_select_response();
+	if (rc < 0)
+		return(rc);
+	if (curfile_structure != 0x00) {
+		fprintf(stderr, "error: EF_PLMNsel is not transparent\n");
+		return(-1);
+	}
+	if (curfile_total_size < 24) {
+		fprintf(stderr,
+		"error: EF_PLMNsel is shorter than spec minimum of 24 bytes\n");
+		return(-1);
+	}
+	if (curfile_total_size > 255) {
+		fprintf(stderr,
+		"error: EF_PLMNsel is longer than our 255 byte limit\n");
+		return(-1);
+	}
+	if (curfile_total_size % 3) {
+		fprintf(stderr,
+		"error: EF_PLMNsel length is not a multiple of 3 bytes\n");
+		return(-1);
+	}
+	return(0);
+}
+
+cmd_plmnsel_dump(argc, argv, outf)
+	char **argv;
+	FILE *outf;
+{
+	int rc, gap_flag;
+	u_char *dp, *endp;
+	char ascbuf[8];
+	unsigned idx, linelen;
+
+	rc = select_ef_plmnsel();
+	if (rc < 0)
+		return(rc);
+	rc = readbin_op(0, curfile_total_size);
+	if (rc < 0)
+		return(rc);
+	dp = sim_resp_data;
+	endp = sim_resp_data + sim_resp_data_len;
+	gap_flag = 0;
+	linelen = 0;
+	for (idx = 0; dp < endp; idx++, dp += 3) {
+		if (dp[0] == 0xFF && dp[1] == 0xFF && dp[2] == 0xFF) {
+			gap_flag = 1;
+			continue;
+		}
+		if (gap_flag) {
+			if (linelen) {
+				putc('\n', outf);
+				linelen = 0;
+			}
+			fprintf(outf, "GAP, continuing at index %u:\n", idx);
+			gap_flag = 0;
+		}
+		if (linelen >= 10) {
+			putc('\n', outf);
+			linelen = 0;
+		}
+		decode_plmn_3bytes(dp, ascbuf, 1);
+		if (linelen)
+			putc(' ', outf);
+		fputs(ascbuf, outf);
+		linelen++;
+	}
+	if (linelen)
+		putc('\n', outf);
+	return(0);
+}
+
+cmd_plmnsel_write(argc, argv)
+	char **argv;
+{
+	int rc;
+	unsigned idx;
+	u_char rec[3];
+
+	rc = select_ef_plmnsel();
+	if (rc < 0)
+		return(rc);
+	idx = strtoul(argv[1], 0, 0);
+	if (idx >= curfile_total_size / 3) {
+		fprintf(stderr, "error: specified index is out of range\n");
+		return(-1);
+	}
+	rc = encode_plmn_3bytes(argv[2], rec);
+	if (rc < 0) {
+		fprintf(stderr, "error: invalid MCC-MNC argument\n");
+		return(rc);
+	}
+	return update_bin_op(idx * 3, rec, 3);
+}
+
+cmd_plmnsel_write_list(argc, argv)
+	char **argv;
+{
+	int rc;
+	u_char buf[255];
+
+	rc = select_ef_plmnsel();
+	if (rc < 0)
+		return(rc);
+	rc = read_plmn_list_from_file(argv[1], buf, curfile_total_size);
+	if (rc < 0)
+		return(rc);
+	return update_bin_op(0, buf, curfile_total_size);
+}
+
+cmd_plmnsel_erase(argc, argv)
+	char **argv;
+{
+	int rc;
+	unsigned idx, start, end, nrec;
+	u_char rec[3];
+
+	rc = select_ef_plmnsel();
+	if (rc < 0)
+		return(rc);
+	nrec = curfile_total_size / 3;
+	start = strtoul(argv[1], 0, 0);
+	if (start >= nrec) {
+		fprintf(stderr,
+			"error: specified starting index is out of range\n");
+		return(-1);
+	}
+	if (!argv[2])
+		end = start;
+	else if (!strcmp(argv[2], "end"))
+		end = nrec - 1;
+	else {
+		end = strtoul(argv[1], 0, 0);
+		if (end >= nrec) {
+			fprintf(stderr,
+			"error: specified ending index is out of range\n");
+			return(-1);
+		}
+		if (start > end) {
+			fprintf(stderr,
+				"error: reverse index range specified\n");
+			return(-1);
+		}
+	}
+	memset(rec, 0xFF, 3);
+	for (idx = start; idx <= end; idx++) {
+		rc = update_bin_op(idx * 3, rec, 3);
+		if (rc < 0)
+			return(rc);
+	}
+	return(0);
+}
+
+cmd_plmnsel_erase_all(argc, argv)
+	char **argv;
+{
+	int rc;
+	u_char ffbuf[255];
+
+	rc = select_ef_plmnsel();
+	if (rc < 0)
+		return(rc);
+	memset(ffbuf, 0xFF, curfile_total_size);
+	return update_bin_op(0, ffbuf, curfile_total_size);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/simtool/pnndump.c	Sun Mar 14 07:11:25 2021 +0000
@@ -0,0 +1,117 @@
+/*
+ * This module implements the pnn-dump command, providing a
+ * user-accessible way to identify MVNO SIMs.
+ */
+
+#include <sys/types.h>
+#include <stdio.h>
+#include "simresp.h"
+#include "curfile.h"
+#include "file_id.h"
+
+select_ef_pnn()
+{
+	int rc;
+
+	rc = select_op(DF_GSM);
+	if (rc < 0)
+		return(rc);
+	rc = select_op(EF_PNN);
+	if (rc < 0)
+		return(rc);
+	rc = parse_ef_select_response();
+	if (rc < 0)
+		return(rc);
+	if (curfile_structure != 0x01) {
+		fprintf(stderr, "error: EF_PNN is not linear fixed\n");
+		return(-1);
+	}
+	if (curfile_record_len < 3) {
+		fprintf(stderr,
+"error: EF_PNN record length is less than the spec minimum of 3 bytes\n");
+		return(-1);
+	}
+	return(0);
+}
+
+static void
+dump_record(recno, outf)
+	unsigned recno;
+	FILE *outf;
+{
+	u_char *dp, *endp;
+	char *name_kw;
+	unsigned ielen, code_byte, nsept;
+	u_char gsm7_buf[288];
+
+	fprintf(outf, "#%u:", recno);
+	dp = sim_resp_data;
+	endp = sim_resp_data + sim_resp_data_len;
+	while (dp < endp) {
+		if (*dp == 0xFF)
+			break;
+		switch (*dp++) {
+		case 0x43:
+			name_kw = "Ln";
+			break;
+		case 0x45:
+			name_kw = "Sn";
+			break;
+		default:
+			fprintf(outf, " unknown-IEI\n");
+			return;
+		}
+		if (dp >= endp) {
+			fprintf(outf, " truncated-IE\n");
+			return;
+		}
+		ielen = *dp++;
+		if (ielen < 1 || ielen > (endp - dp)) {
+			fprintf(outf, " bad-length\n");
+			return;
+		}
+		code_byte = *dp++;
+		ielen--;
+		fprintf(outf, " %s=0x%02X", name_kw, code_byte);
+		if (!ielen)
+			continue;
+		putc(',', outf);
+		if ((code_byte & 0x70) == 0) {
+			nsept = ielen * 8 / 7;
+			gsm7_unpack(dp, gsm7_buf, nsept);
+			dp += ielen;
+			print_gsm7_string_to_file(gsm7_buf, nsept, outf);
+		} else {
+			for (; ielen; ielen--)
+				fprintf(outf, "%02X", *dp++);
+		}
+	}
+	for (; dp < endp; dp++) {
+		if (*dp != 0xFF) {
+			fprintf(outf, " bad-padding\n");
+			return;
+		}
+	}
+	putc('\n', outf);
+}
+
+cmd_pnn_dump(argc, argv, outf)
+	char **argv;
+	FILE *outf;
+{
+	int rc;
+	unsigned recno;
+
+	rc = select_ef_pnn();
+	if (rc < 0)
+		return(rc);
+	for (recno = 1; recno <= curfile_record_count; recno++) {
+		rc = readrec_op(recno, 0x04, curfile_record_len);
+		if (rc < 0)
+			return(rc);
+		if (check_simresp_all_blank())
+			continue;
+		dump_record(recno, outf);
+	}
+	return(0);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/simtool/pnnprog.c	Sun Mar 14 07:11:25 2021 +0000
@@ -0,0 +1,111 @@
+/*
+ * This module implements functions for admin programming of EF_PNN.
+ */
+
+#include <sys/types.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include "curfile.h"
+
+static u_char *
+add_field(dp, bytes_avail, namearg, type)
+	u_char *dp;
+	char *namearg;
+	unsigned bytes_avail, type;
+{
+	u_char gsm7_buf[289];
+	unsigned nsept, noct;
+	int rc;
+
+	if (bytes_avail < 4) {
+		fprintf(stderr,
+			"error: PNN record is too short for name element\n");
+		return(0);
+	}
+	rc = qstring_arg_to_gsm7(namearg, gsm7_buf, (bytes_avail-3) * 8 / 7);
+	if (rc < 0)
+		return(0);
+	nsept = rc;
+	gsm7_buf[nsept] = 0;
+	noct = (nsept * 7 + 7) / 8;
+	*dp++ = type;
+	*dp++ = noct + 1;
+	*dp++ = 0x80 | (nsept & 7);
+	gsm7_pack(gsm7_buf, dp, noct);
+	dp += noct;
+	return dp;
+}
+
+cmd_pnn_write(argc, argv)
+	char **argv;
+{
+	int rc;
+	unsigned recno;
+	u_char record[255], *dp, *endp;
+
+	rc = select_ef_pnn();
+	if (rc < 0)
+		return(rc);
+	recno = strtoul(argv[1], 0, 0);
+	if (recno < 1 || recno > curfile_record_count) {
+		fprintf(stderr, "error: specified record number is invalid\n");
+		return(-1);
+	}
+	dp = record;
+	endp = record + curfile_record_len;
+	dp = add_field(dp, endp - dp, argv[2], 0x43);
+	if (!dp)
+		return(-1);
+	if (argv[3]) {
+		dp = add_field(dp, endp - dp, argv[3], 0x45);
+		if (!dp)
+			return(-1);
+	}
+	while (dp < endp)
+		*dp++ = 0xFF;
+	return update_rec_op(recno, 0x04, record, curfile_record_len);
+}
+
+cmd_pnn_erase(argc, argv)
+	char **argv;
+{
+	int rc;
+	unsigned recno, startrec, endrec;
+	u_char record[255];
+
+	rc = select_ef_pnn();
+	if (rc < 0)
+		return(rc);
+	startrec = strtoul(argv[1], 0, 0);
+	if (startrec < 1 || startrec > curfile_record_count) {
+		fprintf(stderr,
+			"error: specified starting record number is invalid\n");
+		return(-1);
+	}
+	if (!argv[2])
+		endrec = startrec;
+	else if (!strcmp(argv[2], "end"))
+		endrec = curfile_record_count;
+	else {
+		endrec = strtoul(argv[2], 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/readcmd.c	Sun Mar 14 07:11:25 2021 +0000
@@ -0,0 +1,68 @@
+/*
+ * This module implements elementary low-level readbin and readrec commands.
+ */
+
+#include <sys/types.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include "simresp.h"
+#include "curfile.h"
+
+cmd_readbin(argc, argv, outf)
+	char **argv;
+	FILE *outf;
+{
+	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(outf);
+	return(0);
+}
+
+cmd_readrec(argc, argv, outf)
+	char **argv;
+	FILE *outf;
+{
+	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(outf);
+	return(0);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/simtool/readef.c	Sun Mar 14 07:11:25 2021 +0000
@@ -0,0 +1,127 @@
+/*
+ * This module implements the readef command for dumping the complete
+ * content of SIM files.
+ */
+
+#include <sys/types.h>
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include "simresp.h"
+#include "curfile.h"
+
+static void
+hexdump_with_offset(outf, extoff)
+	FILE *outf;
+	unsigned extoff;
+{
+	unsigned off, cc, n, c;
+
+	for (off = 0; off < sim_resp_data_len; off += cc) {
+		fprintf(outf, "%04X:", extoff + off);
+		cc = 16;
+		if (sim_resp_data_len - off < cc)
+			cc = sim_resp_data_len - off;
+		for (n = 0; n < 16; n++) {
+			if (n == 0 || n == 8)
+				putc(' ', outf);
+			putc(' ', outf);
+			if (n < cc)
+				fprintf(outf, "%02X", sim_resp_data[off + n]);
+			else {
+				putc(' ', outf);
+				putc(' ', outf);
+			}
+		}
+		putc(' ', outf);
+		putc(' ', outf);
+		for (n = 0; n < cc; n++) {
+			c = sim_resp_data[off + n];
+			if (c < 0x20 || c > 0x7E)
+				c = '.';
+			putc(c, outf);
+		}
+		putc('\n', outf);
+	}
+}
+
+static
+readef_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);
+		hexdump_with_offset(outf, off);
+	}
+	return(0);
+}
+
+static
+readef_records(outf)
+	FILE *outf;
+{
+	unsigned recno;
+	int rc;
+
+	for (recno = 1; recno <= curfile_record_count; recno++) {
+		fprintf(outf, "Record #%u:\n", recno);
+		rc = readrec_op(recno, 0x04, curfile_record_len);
+		if (rc < 0)
+			return(rc);
+		display_sim_resp_in_hex(outf);
+	}
+	return(0);
+}
+
+cmd_readef(argc, argv, outf)
+	char **argv;
+	FILE *outf;
+{
+	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);
+	show_access_conditions(outf, "UPDATE", sim_resp_data[8] & 0xF);
+	show_access_conditions(outf, "READ & SEEK", sim_resp_data[8] >> 4);
+	show_access_conditions(outf, "INCREASE", sim_resp_data[9] >> 4);
+	show_access_conditions(outf, "INVALIDATE", sim_resp_data[10] & 0xF);
+	show_access_conditions(outf, "REHABILITATE", sim_resp_data[10] >> 4);
+	fprintf(outf, "File status: %02X\n", sim_resp_data[11]);
+	switch (curfile_structure) {
+	case 0x00:
+		fprintf(outf, "Transparent EF of %u byte(s)\n",
+			curfile_total_size);
+		return readef_transparent(outf);
+	case 0x01:
+		fprintf(outf, "%u records of %u bytes (linear fixed)\n",
+			curfile_record_count, curfile_record_len);
+		return readef_records(outf);
+	case 0x03:
+		fprintf(outf, "%u records of %u bytes (cyclic)\n",
+			curfile_record_count, curfile_record_len);
+		return readef_records(outf);
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/simtool/readops.c	Sun Mar 14 07:11:25 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/restorebin.c	Sun Mar 14 07:11:25 2021 +0000
@@ -0,0 +1,136 @@
+/*
+ * This module implements the restore-file command; this command
+ * reads binary files previously saved with the savebin command
+ * and writes the backed-up bits back to the SIM.
+ */
+
+#include <sys/types.h>
+#include <sys/file.h>
+#include <sys/stat.h>
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include "curfile.h"
+
+restore_bin_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);
+}
+
+restore_bin_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);
+}
+
+restore_bin_cyclic(data)
+	u_char *data;
+{
+	unsigned count;
+	u_char *dp;
+	int rc;
+
+	dp = data + curfile_total_size;
+	for (count = 0; count < curfile_record_count; count++) {
+		dp -= curfile_record_len;
+		rc = update_rec_op(0, 0x03, dp, curfile_record_len);
+		if (rc < 0)
+			return(rc);
+	}
+	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_bin_transparent(databuf);
+		break;
+	case 0x01:
+		/* record-based */
+		rc = restore_bin_records(databuf);
+		break;
+	case 0x03:
+		/* cyclic */
+		rc = restore_bin_cyclic(databuf);
+		break;
+	}
+	free(databuf);
+	return(rc);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/simtool/savebin.c	Sun Mar 14 07:11:25 2021 +0000
@@ -0,0 +1,119 @@
+/*
+ * This module implements commands for saving SIM file content
+ * in UNIX host files.
+ */
+
+#include <sys/types.h>
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.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);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/simtool/script.c	Sun Mar 14 07:11:25 2021 +0000
@@ -0,0 +1,39 @@
+/*
+ * This module implements the exec command, which is our scripting facility.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+
+extern FILE *open_script_input_file();
+
+cmd_exec(argc, argv)
+	char **argv;
+{
+	FILE *f;
+	char linebuf[512], *cp;
+	int lineno, retval = 0;
+
+	f = open_script_input_file(argv[1]);
+	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	Sun Mar 14 07:11:25 2021 +0000
@@ -0,0 +1,263 @@
+#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"
+
+elem_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 == 0x9404)
+		return(0);
+	if ((sim_resp_sw & 0xFF00) == 0x9F00)
+		return(1);
+	fprintf(stderr,
+		"error or unexpected SW response to SELECT of 0x%04X: %04X\n",
+		file_id, sim_resp_sw);
+	return(-1);
+}
+
+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(outf, name, byte)
+	FILE *outf;
+	char *name;
+	unsigned byte;
+{
+	fprintf(outf, "Status of %s: %s, %u attempts left\n", name,
+		byte & 0x80 ? "initialized" : "not initialized",
+		byte & 0x0F);
+}
+
+void
+show_access_conditions(outf, oper_name, cond_code)
+	FILE *outf;
+	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"};
+
+	fprintf(outf, "Access condition for %s: %s\n", oper_name,
+		cond_names[cond_code]);
+}
+
+cmd_select(argc, argv, outf)
+	char **argv;
+	FILE *outf;
+{
+	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:
+		fprintf(outf, "File type: MF\n");
+		goto mf_or_df;
+	case 0x02:
+		fprintf(outf, "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);
+		}
+		fprintf(outf, "File characteristics: %02X\n",
+			sim_resp_data[13]);
+		fprintf(outf, "Number of DF children: %u\n", sim_resp_data[14]);
+		fprintf(outf, "Number of EF children: %u\n", sim_resp_data[15]);
+		fprintf(outf, "Number of secret codes: %u\n",
+			sim_resp_data[16]);
+		show_secret_code_status(outf, "PIN1", sim_resp_data[18]);
+		show_secret_code_status(outf, "PUK1", sim_resp_data[19]);
+		show_secret_code_status(outf, "PIN2", sim_resp_data[20]);
+		show_secret_code_status(outf, "PUK2", sim_resp_data[21]);
+		break;
+	case 0x04:
+		fprintf(outf, "File type: EF\n");
+		curfile_total_size = (sim_resp_data[2] << 8) | sim_resp_data[3];
+		fprintf(outf, "File size: %u\n", curfile_total_size);
+		curfile_structure = sim_resp_data[13];
+		switch (curfile_structure) {
+		case 0x00:
+			fprintf(outf, "Structure: transparent\n");
+			break;
+		case 0x01:
+			fprintf(outf, "Structure: linear fixed\n");
+			goto ef_record_based;
+		case 0x03:
+			fprintf(outf, "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);
+			}
+			fprintf(outf, "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;
+				fprintf(outf, "Number of records: %u\n",
+					curfile_record_count);
+			} else
+				curfile_record_count = 0;
+			break;
+		default:
+			fprintf(outf, "Structure: %02X (unknown)\n",
+				curfile_structure);
+		}
+		fprintf(outf, "File status: %02X\n", sim_resp_data[11]);
+		show_access_conditions(outf, "UPDATE", sim_resp_data[8] & 0xF);
+		show_access_conditions(outf, "READ & SEEK",
+					sim_resp_data[8] >> 4);
+		show_access_conditions(outf, "INCREASE", sim_resp_data[9] >> 4);
+		show_access_conditions(outf, "INVALIDATE",
+					sim_resp_data[10] & 0xF);
+		show_access_conditions(outf, "REHABILITATE",
+					sim_resp_data[10] >> 4);
+		break;
+	default:
+		fprintf(outf, "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;
+		if (curfile_record_count > 255) {
+			fprintf(stderr,
+			"error: EF record count exceeds protocol limit\n");
+			return(-1);
+		}
+		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/sjs1_hacks.c	Sun Mar 14 07:11:25 2021 +0000
@@ -0,0 +1,114 @@
+/*
+ * This module implements a few special commands for the recently
+ * discontinued sysmoUSIM-SJS1 card model from Sysmocom.  These commands
+ * are NOT applicable to the successor sysmoISIM-SJA2 card model!
+ */
+
+#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"
+
+/*
+ * SJS1 is natively a UICC, supporting the classic GSM 11.11 SIM protocol
+ * only as a backward compatibility mode.  The makers of that UICC CardOS
+ * clearly did not want people to do administrative programming via the
+ * GSM 11.11 SIM protocol, instead their vision was that admin programming
+ * should only be done in UICC mode.  Toward this end, SJS1 cards do not
+ * accept VERIFY CHV commands with CLA=0xA0 P2=0x0A for ADM1 authentication,
+ * instead they only accept VERIFY PIN with CLA=0x00 for this purpose.
+ *
+ * They did leave one open loophole, however: if the UICC-style VERIFY PIN
+ * command with P2=0x0A for ADM1 authentication is given as the very first
+ * command in the card session, then it can be followed either by other
+ * UICC protocol commands (making a UICC card session), or by CLA=0xA0
+ * protocol commands, making a GSM 11.11 SIM session with ADM1 authentication.
+ * In other words, they allow one special exception to the general rule
+ * where SIM and UICC protocol commands are never allowed to mix in the
+ * same card session.
+ */
+
+cmd_verify_sjs1_adm1(argc, argv)
+	char **argv;
+{
+	u_char cmd[13];
+	int rc;
+
+	/* UICC-style VERIFY PIN command APDU */
+	cmd[0] = 0x00;
+	cmd[1] = 0x20;
+	cmd[2] = 0x00;
+	cmd[3] = 0x0A;
+	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);
+}
+
+/*
+ * Early sysmoUSIM-SJS1 cards (those sold in 2017, but not the very last
+ * ones sold in late 2020) were shipped with a misprogrammed MSISDN record.
+ * Our fix-sysmo-msisdn command fixes this particular misprogramming.
+ */
+
+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/smserase.c	Sun Mar 14 07:11:25 2021 +0000
@@ -0,0 +1,121 @@
+/*
+ * This module implements the sms-erase family of commands.
+ */
+
+#include <sys/types.h>
+#include <string.h>
+#include <strings.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include "curfile.h"
+#include "file_id.h"
+
+#define	SMS_RECORD_LEN	176
+
+static
+select_ef_sms()
+{
+	int rc;
+
+	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) {
+		fprintf(stderr, "error: EF_SMS is not linear fixed\n");
+		return(-1);
+	}
+	if (curfile_record_len != SMS_RECORD_LEN) {
+		fprintf(stderr,
+		"error: EF_SMS has record length of %u bytes, expected 176\n",
+			curfile_record_len);
+		return(-1);
+	}
+	return(0);
+}
+
+cmd_sms_erase_all(argc, argv)
+	char **argv;
+{
+	int rc;
+	unsigned recno;
+	u_char record[SMS_RECORD_LEN];
+
+	rc = select_ef_sms();
+	if (rc < 0)
+		return(rc);
+	memset(record, 0xFF, SMS_RECORD_LEN);
+	record[0] = 0;
+	for (recno = 1; recno <= curfile_record_count; recno++) {
+		rc = update_rec_op(recno, 0x04, record, SMS_RECORD_LEN);
+		if (rc < 0)
+			return(rc);
+	}
+	return(0);
+}
+
+cmd_sms_erase_one(argc, argv)
+	char **argv;
+{
+	int rc;
+	unsigned recno;
+	u_char record[SMS_RECORD_LEN];
+
+	rc = select_ef_sms();
+	if (rc < 0)
+		return(rc);
+	recno = strtoul(argv[1], 0, 0);
+	if (recno < 1 || recno > curfile_record_count) {
+		fprintf(stderr, "error: specified record number is invalid\n");
+		return(-1);
+	}
+	memset(record, 0xFF, SMS_RECORD_LEN);
+	record[0] = 0;
+	return update_rec_op(recno, 0x04, record, SMS_RECORD_LEN);
+}
+
+cmd_sms_erase_range(argc, argv)
+	char **argv;
+{
+	int rc;
+	unsigned recno, startrec, endrec;
+	u_char record[SMS_RECORD_LEN];
+
+	rc = select_ef_sms();
+	if (rc < 0)
+		return(rc);
+	startrec = strtoul(argv[1], 0, 0);
+	if (startrec < 1 || startrec > curfile_record_count) {
+		fprintf(stderr,
+			"error: specified starting record number is invalid\n");
+		return(-1);
+	}
+	if (!strcmp(argv[2], "end"))
+		endrec = curfile_record_count;
+	else {
+		endrec = strtoul(argv[2], 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, SMS_RECORD_LEN);
+	record[0] = 0;
+	for (recno = startrec; recno <= endrec; recno++) {
+		rc = update_rec_op(recno, 0x04, record, SMS_RECORD_LEN);
+		if (rc < 0)
+			return(rc);
+	}
+	return(0);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/simtool/smsp_common.c	Sun Mar 14 07:11:25 2021 +0000
@@ -0,0 +1,33 @@
+/*
+ * This module implements some common functions for working with EF_SMSP.
+ */
+
+#include <stdio.h>
+#include "curfile.h"
+#include "file_id.h"
+
+select_ef_smsp()
+{
+	int rc;
+
+	rc = select_op(DF_TELECOM);
+	if (rc < 0)
+		return(rc);
+	rc = select_op(EF_SMSP);
+	if (rc < 0)
+		return(rc);
+	rc = parse_ef_select_response();
+	if (rc < 0)
+		return(rc);
+	if (curfile_structure != 0x01) {
+		fprintf(stderr, "error: EF_SMSP is not linear fixed\n");
+		return(-1);
+	}
+	if (curfile_record_len < 28) {
+		fprintf(stderr,
+	"error: EF_SMSP has record length of %u bytes, less than minimum 28\n",
+			curfile_record_len);
+		return(-1);
+	}
+	return(0);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/simtool/smsp_dump.c	Sun Mar 14 07:11:25 2021 +0000
@@ -0,0 +1,130 @@
+/*
+ * This module implements intelligent dumping of EF_SMSP (smsp-dump).
+ */
+
+#include <sys/types.h>
+#include <stdio.h>
+#include "simresp.h"
+#include "curfile.h"
+
+static
+check_blank_area(dp, endp)
+	u_char *dp, *endp;
+{
+	while (dp < endp)
+		if (*dp++ != 0xFF)
+			return(-1);
+	return(0);
+}
+
+static void
+dump_da_field(binaddr, outf)
+	u_char *binaddr;
+	FILE *outf;
+{
+	char digits[21];
+
+	fputs("DA=", outf);
+	if (binaddr[0] < 1 || binaddr[0] > 20) {
+malformed:	fputs("malformed ", outf);
+		return;
+	}
+	if ((binaddr[0] & 1) && (binaddr[(binaddr[0] >> 1) + 2] & 0xF0) != 0xF0)
+		goto malformed;
+	if (check_blank_area(binaddr + 2 + ((binaddr[0] + 1) >> 1),
+			     binaddr + 12) < 0)
+		goto malformed;
+	/* all checks passed */
+	decode_address_digits(binaddr + 2, digits, binaddr[0]);
+	fprintf(outf, "%s,0x%02X ", digits, binaddr[1]);
+}
+
+static void
+dump_sca_field(binaddr, outf)
+	u_char *binaddr;
+	FILE *outf;
+{
+	char digits[21];
+	int rc;
+
+	fputs("SC=", outf);
+	if (binaddr[0] < 2 || binaddr[0] > 11) {
+malformed:	fputs("malformed ", outf);
+		return;
+	}
+	rc = decode_phone_number(binaddr + 2, binaddr[0] - 1, digits);
+	if (rc < 0)
+		goto malformed;
+	rc = check_blank_area(binaddr + 1 + binaddr[0], binaddr + 12);
+	if (rc < 0)
+		goto malformed;
+	/* all checks passed */
+	fprintf(outf, "%s,0x%02X ", digits, binaddr[1]);
+}
+
+static void
+dump_record(recno, outf)
+	unsigned recno;
+	FILE *outf;
+{
+	int rc;
+	unsigned textlen;
+	u_char *fixp;
+
+	fprintf(outf, "#%u: ", recno);
+	if (sim_resp_data_len > 28) {
+		rc = validate_alpha_field(sim_resp_data,
+					  sim_resp_data_len - 28,
+					  &textlen);
+		if (rc < 0) {
+malformed:		fprintf(outf, "malformed record\n");
+			return;
+		}
+	} else
+		textlen = 0;
+	fixp = sim_resp_data + sim_resp_data_len - 28;
+	if ((fixp[0] & 0xE0) != 0xE0)
+		goto malformed;
+	if ((fixp[0] & 0x01) && check_blank_area(fixp + 1, fixp + 13) < 0)
+		goto malformed;
+	if ((fixp[0] & 0x02) && check_blank_area(fixp + 13, fixp + 25) < 0)
+		goto malformed;
+	if ((fixp[0] & 0x04) && fixp[25] != 0xFF)
+		goto malformed;
+	if ((fixp[0] & 0x08) && fixp[26] != 0xFF)
+		goto malformed;
+	if ((fixp[0] & 0x10) && fixp[27] != 0xFF)
+		goto malformed;
+	/* basic checks passed, emit present fields */
+	if (!(fixp[0] & 0x01))
+		dump_da_field(fixp + 1, outf);
+	if (!(fixp[0] & 0x02))
+		dump_sca_field(fixp + 13, outf);
+	if (!(fixp[0] & 0x04))
+		fprintf(outf, "PID=0x%02X ", fixp[25]);
+	if (!(fixp[0] & 0x08))
+		fprintf(outf, "DCS=0x%02X ", fixp[26]);
+	if (!(fixp[0] & 0x10))
+		fprintf(outf, "VP=%u ", fixp[27]);
+	print_alpha_field(sim_resp_data, textlen, outf);
+	putc('\n', outf);
+}
+
+cmd_smsp_dump(argc, argv, outf)
+	char **argv;
+	FILE *outf;
+{
+	int rc;
+	unsigned recno;
+
+	rc = select_ef_smsp();
+	if (rc < 0)
+		return(rc);
+	for (recno = 1; recno <= curfile_record_count; recno++) {
+		rc = readrec_op(recno, 0x04, curfile_record_len);
+		if (rc < 0)
+			return(rc);
+		dump_record(recno, outf);
+	}
+	return(0);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/simtool/smsp_erase.c	Sun Mar 14 07:11:25 2021 +0000
@@ -0,0 +1,88 @@
+/*
+ * This module implements the smsp-erase family of commands.
+ */
+
+#include <sys/types.h>
+#include <string.h>
+#include <strings.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include "curfile.h"
+
+cmd_smsp_erase_all(argc, argv)
+	char **argv;
+{
+	int rc;
+	unsigned recno;
+	u_char record[255];
+
+	rc = select_ef_smsp();
+	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_smsp_erase_one(argc, argv)
+	char **argv;
+{
+	int rc;
+	unsigned recno;
+	u_char record[255];
+
+	rc = select_ef_smsp();
+	if (rc < 0)
+		return(rc);
+	recno = strtoul(argv[1], 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_smsp_erase_range(argc, argv)
+	char **argv;
+{
+	int rc;
+	unsigned recno, startrec, endrec;
+	u_char record[255];
+
+	rc = select_ef_smsp();
+	if (rc < 0)
+		return(rc);
+	startrec = strtoul(argv[1], 0, 0);
+	if (startrec < 1 || startrec > curfile_record_count) {
+		fprintf(stderr,
+			"error: specified starting record number is invalid\n");
+		return(-1);
+	}
+	if (!strcmp(argv[2], "end"))
+		endrec = curfile_record_count;
+	else {
+		endrec = strtoul(argv[2], 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/smsp_restore.c	Sun Mar 14 07:11:25 2021 +0000
@@ -0,0 +1,248 @@
+/*
+ * This module implements the smsp-restore command.
+ */
+
+#include <sys/types.h>
+#include <ctype.h>
+#include <string.h>
+#include <strings.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include "curfile.h"
+
+extern FILE *open_script_input_file();
+
+extern char *alpha_from_file_qstring();
+extern char *alpha_from_file_hex();
+
+static char *
+parse_da(cp, bina, filename_for_errs, lineno_for_errs)
+	char *cp, *filename_for_errs;
+	u_char *bina;
+{
+	u_char digits[20];
+	unsigned ndigits, num_digit_bytes;
+	int c;
+
+	if (digit_char_to_gsm(*cp) < 0) {
+inv_syntax:	fprintf(stderr, "%s line %d: DA= parameter invalid syntax\n",
+			filename_for_errs, lineno_for_errs);
+		return(0);
+	}
+	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(0);
+		}
+		digits[ndigits] = c;
+	}
+	bina[0] = ndigits;
+	if (ndigits & 1)
+		digits[ndigits++] = 0xF;
+	num_digit_bytes = ndigits >> 1;
+	pack_digit_bytes(digits, bina + 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;
+	bina[1] = strtoul(cp, 0, 16);
+	cp += 5;
+	while (isspace(*cp))
+		cp++;
+	return(cp);
+}
+
+static char *
+parse_sc(cp, bina, filename_for_errs, lineno_for_errs)
+	char *cp, *filename_for_errs;
+	u_char *bina;
+{
+	u_char digits[20];
+	unsigned ndigits, num_digit_bytes;
+	int c;
+
+	if (digit_char_to_gsm(*cp) < 0) {
+inv_syntax:	fprintf(stderr, "%s line %d: SC= parameter invalid syntax\n",
+			filename_for_errs, lineno_for_errs);
+		return(0);
+	}
+	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(0);
+		}
+		digits[ndigits] = c;
+	}
+	if (ndigits & 1)
+		digits[ndigits++] = 0xF;
+	num_digit_bytes = ndigits >> 1;
+	bina[0] = num_digit_bytes + 1;
+	pack_digit_bytes(digits, bina + 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;
+	bina[1] = strtoul(cp, 0, 16);
+	cp += 5;
+	while (isspace(*cp))
+		cp++;
+	return(cp);
+}
+
+static
+process_record(line, filename_for_errs, lineno_for_errs)
+	char *line, *filename_for_errs;
+{
+	unsigned recno;
+	u_char record[255], *fixp;
+	char *cp;
+
+	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 - 28;
+	if (!strncasecmp(cp, "DA=", 3)) {
+		cp += 3;
+		cp = parse_da(cp, fixp + 1, filename_for_errs, lineno_for_errs);
+		if (!cp)
+			return(-1);
+		fixp[0] &= 0xFE;
+	}
+	if (!strncasecmp(cp, "SC=", 3)) {
+		cp += 3;
+		cp = parse_sc(cp, fixp+13, filename_for_errs, lineno_for_errs);
+		if (!cp)
+			return(-1);
+		fixp[0] &= 0xFD;
+	}
+	if (!strncasecmp(cp, "PID=", 4)) {
+		cp += 4;
+		if (!isdigit(*cp)) {
+			fprintf(stderr,
+				"%s line %d: PID= parameter invalid syntax\n",
+				filename_for_errs, lineno_for_errs);
+			return(-1);
+		}
+		fixp[25] = strtoul(cp, 0, 0);
+		fixp[0] &= 0xFB;
+		while (*cp && !isspace(*cp))
+			cp++;
+		while (isspace(*cp))
+			cp++;
+	}
+	if (!strncasecmp(cp, "DCS=", 4)) {
+		cp += 4;
+		if (!isdigit(*cp)) {
+			fprintf(stderr,
+				"%s line %d: DCS= parameter invalid syntax\n",
+				filename_for_errs, lineno_for_errs);
+			return(-1);
+		}
+		fixp[26] = strtoul(cp, 0, 0);
+		fixp[0] &= 0xF7;
+		while (*cp && !isspace(*cp))
+			cp++;
+		while (isspace(*cp))
+			cp++;
+	}
+	if (!strncasecmp(cp, "VP=", 3)) {
+		cp += 3;
+		if (!isdigit(*cp)) {
+			fprintf(stderr,
+				"%s line %d: VP= parameter invalid syntax\n",
+				filename_for_errs, lineno_for_errs);
+			return(-1);
+		}
+		fixp[27] = strtoul(cp, 0, 0);
+		fixp[0] &= 0xEF;
+		while (*cp && !isspace(*cp))
+			cp++;
+		while (isspace(*cp))
+			cp++;
+	}
+	if (*cp == '"') {
+		cp++;
+		cp = alpha_from_file_qstring(cp, record,
+					     curfile_record_len - 28,
+					     filename_for_errs,
+					     lineno_for_errs);
+		if (!cp)
+			return(-1);
+	} else if (!strncasecmp(cp, "HEX", 3)) {
+		cp += 3;
+		while (isspace(*cp))
+			cp++;
+		cp = alpha_from_file_hex(cp, record, curfile_record_len - 28,
+					 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_smsp_restore(argc, argv)
+	char **argv;
+{
+	int rc;
+	FILE *inf;
+	int lineno;
+	char linebuf[1024];
+
+	rc = select_ef_smsp();
+	if (rc < 0)
+		return(rc);
+	inf = open_script_input_file(argv[1]);
+	if (!inf) {
+		perror(argv[1]);
+		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[1], lineno);
+			fclose(inf);
+			return(-1);
+		}
+		if (linebuf[0] != '#' || !isdigit(linebuf[1]))
+			continue;
+		rc = process_record(linebuf, argv[1], lineno);
+		if (rc < 0) {
+			fclose(inf);
+			return(rc);
+		}
+	}
+	fclose(inf);
+	return(0);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/simtool/smsp_set.c	Sun Mar 14 07:11:25 2021 +0000
@@ -0,0 +1,167 @@
+/*
+ * This module implements the user-oriented smsp-set and smsp-set-tag commands.
+ */
+
+#include <sys/types.h>
+#include <ctype.h>
+#include <string.h>
+#include <strings.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include "curfile.h"
+
+static
+set_param_da(arg, fixp)
+	char *arg;
+	u_char *fixp;
+{
+	int rc;
+
+	rc = encode_phone_number_arg(arg, fixp + 1, 1);
+	if (rc < 0)
+		return(rc);
+	fixp[0] &= 0xFE;
+	return(0);
+}
+
+static
+set_param_sc(arg, fixp)
+	char *arg;
+	u_char *fixp;
+{
+	int rc;
+
+	rc = encode_phone_number_arg(arg, fixp + 13, 0);
+	if (rc < 0)
+		return(rc);
+	fixp[0] &= 0xFD;
+	return(0);
+}
+
+static
+set_param_pid(arg, fixp)
+	char *arg;
+	u_char *fixp;
+{
+	char *endp;
+
+	if (!isdigit(*arg)) {
+inv:		fprintf(stderr, "error: invalid PID= parameter\n");
+		return(-1);
+	}
+	fixp[25] = strtoul(arg, &endp, 0);
+	if (*endp)
+		goto inv;
+	fixp[0] &= 0xFB;
+	return(0);
+}
+
+static
+set_param_dcs(arg, fixp)
+	char *arg;
+	u_char *fixp;
+{
+	char *endp;
+
+	if (!isdigit(*arg)) {
+inv:		fprintf(stderr, "error: invalid DCS= parameter\n");
+		return(-1);
+	}
+	fixp[26] = strtoul(arg, &endp, 0);
+	if (*endp)
+		goto inv;
+	fixp[0] &= 0xF7;
+	return(0);
+}
+
+static
+set_param_vp(arg, fixp)
+	char *arg;
+	u_char *fixp;
+{
+	char *endp;
+
+	if (!isdigit(*arg)) {
+inv:		fprintf(stderr, "error: invalid VP= parameter\n");
+		return(-1);
+	}
+	fixp[27] = strtoul(arg, &endp, 0);
+	if (*endp)
+		goto inv;
+	fixp[0] &= 0xEF;
+	return(0);
+}
+
+static
+set_param(arg, fixp)
+	char *arg;
+	u_char *fixp;
+{
+	if (!strncasecmp(arg, "DA=", 3))
+		return set_param_da(arg + 3, fixp);
+	if (!strncasecmp(arg, "SC=", 3))
+		return set_param_sc(arg + 3, fixp);
+	if (!strncasecmp(arg, "PID=", 4))
+		return set_param_pid(arg + 4, fixp);
+	if (!strncasecmp(arg, "DCS=", 4))
+		return set_param_dcs(arg + 4, fixp);
+	if (!strncasecmp(arg, "VP=", 3))
+		return set_param_vp(arg + 3, fixp);
+	fprintf(stderr, "error: non-understood parameter \"%s\"\n", arg);
+	return(-1);
+}
+
+cmd_smsp_set(argc, argv)
+	char **argv;
+{
+	int rc;
+	unsigned recno;
+	u_char record[255], *fixp;
+	char **ap;
+
+	rc = select_ef_smsp();
+	if (rc < 0)
+		return(rc);
+	recno = strtoul(argv[1], 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 - 28;
+	for (ap = argv + 2; *ap; ap++) {
+		rc = set_param(*ap, fixp);
+		if (rc < 0)
+			return(rc);
+	}
+	return update_rec_op(recno, 0x04, record, curfile_record_len);
+}
+
+cmd_smsp_set_tag(argc, argv)
+	char **argv;
+{
+	int rc;
+	unsigned recno;
+	u_char record[255], *fixp;
+	char **ap;
+
+	rc = select_ef_smsp();
+	if (rc < 0)
+		return(rc);
+	recno = strtoul(argv[1], 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);
+	rc = qstring_arg_to_gsm7(argv[2], record, curfile_record_len - 28);
+	if (rc < 0)
+		return(rc);
+	fixp = record + curfile_record_len - 28;
+	for (ap = argv + 3; *ap; ap++) {
+		rc = set_param(*ap, fixp);
+		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/sstlist.c	Sun Mar 14 07:11:25 2021 +0000
@@ -0,0 +1,70 @@
+/*
+ * This module implements the sst command, listing the SIM Service Table
+ * in a human-readable, yet very compact form: just a list of activated
+ * (or allocated but not activated, specially marked) service numbers.
+ */
+
+#include <sys/types.h>
+#include <stdio.h>
+#include "simresp.h"
+#include "curfile.h"
+#include "file_id.h"
+
+cmd_sst(argc, argv, outf)
+	char **argv;
+	FILE *outf;
+{
+	int rc;
+	unsigned byte, pos, code, nserv, linelen;
+
+	rc = select_op(DF_GSM);
+	if (rc < 0)
+		return(rc);
+	rc = select_op(EF_SST);
+	if (rc < 0)
+		return(rc);
+	rc = parse_ef_select_response();
+	if (rc < 0)
+		return(rc);
+	if (curfile_structure != 0x00) {
+		fprintf(stderr, "error: EF_SST is not transparent\n");
+		return(-1);
+	}
+	if (curfile_total_size < 2) {
+		fprintf(stderr,
+		"error: EF_SST is shorter than spec minimum of 2 bytes\n");
+		return(-1);
+	}
+	if (curfile_total_size > 256) {
+		fprintf(stderr,
+			"error: EF_SST is longer than our 256 byte limit\n");
+		return(-1);
+	}
+	rc = readbin_op(0, curfile_total_size);
+	if (rc < 0)
+		return(rc);
+	linelen = 0;
+	for (byte = 0, nserv = 1; byte < curfile_total_size; byte++) {
+		for (pos = 0; pos < 8; pos += 2, nserv++) {
+			code = (sim_resp_data[byte] >> pos) & 3;
+			if (!(code & 1))
+				continue;
+			if (linelen > 73) {
+				putc('\n', outf);
+				linelen = 0;
+			}
+			if (linelen) {
+				putc(' ', outf);
+				linelen++;
+			}
+			linelen += fprintf(outf, "%u", nserv);
+			if (!(code & 2)) {
+				putc('^', outf);
+				linelen++;
+			}
+		}
+	}
+	if (linelen)
+		putc('\n', outf);
+	return(0);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/simtool/sstprog.c	Sun Mar 14 07:11:25 2021 +0000
@@ -0,0 +1,98 @@
+/*
+ * This module implements the write-sst command for admin-level
+ * programming of SIM cards.
+ */
+
+#include <sys/types.h>
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include "curfile.h"
+#include "file_id.h"
+
+extern FILE *open_script_input_file();
+
+cmd_write_sst(argc, argv)
+	char **argv;
+{
+	u_char sst[255];
+	FILE *inf;
+	int lineno, rc;
+	char linebuf[1024], *cp, *np;
+	unsigned num, code, max_serv;
+
+	rc = select_op(DF_GSM);
+	if (rc < 0)
+		return(rc);
+	rc = select_op(EF_SST);
+	if (rc < 0)
+		return(rc);
+	rc = parse_ef_select_response();
+	if (rc < 0)
+		return(rc);
+	if (curfile_structure != 0x00) {
+		fprintf(stderr, "error: EF_SST is not transparent\n");
+		return(-1);
+	}
+	if (curfile_total_size < 2) {
+		fprintf(stderr,
+		"error: EF_SST is shorter than spec minimum of 2 bytes\n");
+		return(-1);
+	}
+	if (curfile_total_size > 255) {
+		fprintf(stderr,
+			"error: EF_SST is longer than our 255 byte limit\n");
+		return(-1);
+	}
+	memset(sst, 0, curfile_total_size);
+	max_serv = curfile_total_size * 4;
+	inf = open_script_input_file(argv[1]);
+	if (!inf) {
+		perror(argv[1]);
+		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[1], lineno);
+			fclose(inf);
+			return(-1);
+		}
+		for (cp = linebuf; ; ) {
+			while (isspace(*cp))
+				cp++;
+			if (*cp == '\0' || *cp == '#')
+				break;
+			if (!isdigit(*cp)) {
+inv_syntax:			fprintf(stderr, "%s line %d: invalid syntax\n",
+					argv[1], lineno);
+				fclose(inf);
+				return(-1);
+			}
+			num = strtoul(cp, 0, 10);
+			while (isdigit(*cp))
+				cp++;
+			if (*cp == '^') {
+				cp++;
+				code = 1;
+			} else
+				code = 3;
+			if (*cp && !isspace(*cp))
+				goto inv_syntax;
+			if (num < 1 || num > max_serv) {
+				fprintf(stderr,
+				"%s line %d: service number is out of range\n",
+					argv[1], lineno);
+				fclose(inf);
+				return(-1);
+			}
+			num--;
+			sst[num >> 2] |= code << ((num & 3) << 1);
+		}
+	}
+	fclose(inf);
+	return update_bin_op(0, sst, curfile_total_size);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/simtool/stktest.c	Sun Mar 14 07:11:25 2021 +0000
@@ -0,0 +1,81 @@
+/*
+ * This module implements some commands for testing SIM Toolkit functionality,
+ * just enough to be able to simulate SMS-PP SIM data download operations.
+ */
+
+#include <sys/types.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include "simresp.h"
+
+cmd_terminal_profile(argc, argv)
+	char **argv;
+{
+	u_char cmd[260];
+	int rc;
+	unsigned len;
+
+	rc = decode_hex_data_from_string(argv[1], cmd + 5, 1, 255);
+	if (rc < 0)
+		return(rc);
+	len = rc;
+	/* TERMINAL PROFILE command APDU */
+	cmd[0] = 0xA0;
+	cmd[1] = 0x10;
+	cmd[2] = 0;
+	cmd[3] = 0;
+	cmd[4] = len;
+	rc = apdu_exchange(cmd, len + 5);
+	if (rc < 0)
+		return(rc);
+	printf("%04X\n", sim_resp_sw);
+	return(0);
+}
+
+cmd_envelope(argc, argv)
+	char **argv;
+{
+	u_char cmd[260];
+	int rc;
+	unsigned len;
+
+	rc = read_hex_data_file(argv[1], cmd + 5, 255);
+	if (rc < 0)
+		return(rc);
+	len = rc;
+	/* ENVELOPE command APDU */
+	cmd[0] = 0xA0;
+	cmd[1] = 0xC2;
+	cmd[2] = 0;
+	cmd[3] = 0;
+	cmd[4] = len;
+	rc = apdu_exchange(cmd, len + 5);
+	if (rc < 0)
+		return(rc);
+	printf("%04X\n", sim_resp_sw);
+	return(0);
+}
+
+cmd_envelope_imm(argc, argv)
+	char **argv;
+{
+	u_char cmd[260];
+	int rc;
+	unsigned len;
+
+	rc = decode_hex_data_from_string(argv[1], cmd + 5, 1, 255);
+	if (rc < 0)
+		return(rc);
+	len = rc;
+	/* ENVELOPE command APDU */
+	cmd[0] = 0xA0;
+	cmd[1] = 0xC2;
+	cmd[2] = 0;
+	cmd[3] = 0;
+	cmd[4] = len;
+	rc = apdu_exchange(cmd, len + 5);
+	if (rc < 0)
+		return(rc);
+	printf("%04X\n", sim_resp_sw);
+	return(0);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/simtool/telsum.c	Sun Mar 14 07:11:25 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/usersum.c	Sun Mar 14 07:11:25 2021 +0000
@@ -0,0 +1,189 @@
+/*
+ * This module implements the user-sum (summary info) command.
+ */
+
+#include <sys/types.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include "simresp.h"
+#include "curfile.h"
+#include "file_id.h"
+
+#define	SST_BYTES_USED	15
+
+static
+read_sst(sstbuf)
+	u_char *sstbuf;
+{
+	int rc;
+	unsigned rdlen;
+
+	rc = select_op(DF_GSM);
+	if (rc < 0)
+		return(rc);
+	rc = select_op(EF_SST);
+	if (rc < 0)
+		return(rc);
+	rc = parse_ef_select_response();
+	if (rc < 0)
+		return(rc);
+	if (curfile_structure != 0x00) {
+		fprintf(stderr, "error: EF_SST is not transparent\n");
+		return(-1);
+	}
+	if (curfile_total_size < 2) {
+		fprintf(stderr,
+		"error: EF_SST is shorter than spec minimum of 2 bytes\n");
+		return(-1);
+	}
+	rdlen = curfile_total_size;
+	if (rdlen > SST_BYTES_USED)
+		rdlen = SST_BYTES_USED;
+	rc = readbin_op(0, rdlen);
+	if (rc < 0)
+		return(rc);
+	bcopy(sim_resp_data, sstbuf, rdlen);
+	if (rdlen < SST_BYTES_USED)
+		bzero(sstbuf + rdlen, SST_BYTES_USED - rdlen);
+	return(0);
+}
+
+static
+do_phonebook_file(file_id, ef_name, book_name, outf)
+	unsigned file_id;
+	char *ef_name, *book_name;
+	FILE *outf;
+{
+	int rc;
+
+	rc = select_op(file_id);
+	if (rc < 0)
+		return(rc);
+	rc = parse_ef_select_response();
+	if (rc < 0)
+		return(rc);
+	if (curfile_structure != 0x01 && curfile_structure != 0x03) {
+		fprintf(stderr, "error: %s is not record-structured\n",
+			ef_name);
+		return(-1);
+	}
+	if (curfile_record_len < 14) {
+		fprintf(stderr,
+	"error: %s has record length of %u bytes, less than minimum 14\n",
+			ef_name, curfile_record_len);
+		return(-1);
+	}
+	fprintf(outf, "%s: %u entries, %u bytes of alpha tag\n", book_name,
+		curfile_record_count, curfile_record_len - 14);
+	return(0);
+}
+
+static
+do_sms_store(outf)
+	FILE *outf;
+{
+	int 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);
+	}
+	fprintf(outf, "SMS store: %u entries\n", curfile_record_count);
+	return(0);
+}
+
+static
+do_smsp_store(outf)
+	FILE *outf;
+{
+	int rc;
+
+	rc = select_op(EF_SMSP);
+	if (rc < 0)
+		return(rc);
+	rc = parse_ef_select_response();
+	if (rc < 0)
+		return(rc);
+	if (curfile_structure != 0x01) {
+		fprintf(stderr, "error: EF_SMSP is not linear fixed\n");
+		return(-1);
+	}
+	if (curfile_record_len < 28) {
+		fprintf(stderr,
+	"error: EF_SMSP has record length of %u bytes, less than minimum 14\n",
+			curfile_record_len);
+		return(-1);
+	}
+	fprintf(outf,
+		"SMS parameter store: %u entries, %u bytes of alpha tag\n",
+		curfile_record_count, curfile_record_len - 28);
+	return(0);
+}
+
+cmd_user_sum(argc, argv, outf)
+	char **argv;
+	FILE *outf;
+{
+	int rc;
+	u_char sst[SST_BYTES_USED];
+
+	rc = read_sst(sst);
+	if (rc < 0)
+		return(rc);
+	rc = select_op(DF_TELECOM);
+	if (rc < 0)
+		return(rc);
+	if ((sst[0] & 0x0C) == 0x0C) {
+		rc = do_phonebook_file(EF_ADN, "EF_ADN", "ADN phonebook", outf);
+		if (rc < 0)
+			return(rc);
+	}
+	if ((sst[0] & 0x30) == 0x30) {
+		rc = do_phonebook_file(EF_FDN, "EF_FDN", "FDN phonebook", outf);
+		if (rc < 0)
+			return(rc);
+	}
+	if ((sst[0] & 0xC0) == 0xC0) {
+		rc = do_sms_store(outf);
+		if (rc < 0)
+			return(rc);
+	}
+	if ((sst[1] & 0x03) == 0x03)
+		fprintf(outf, "AoC service present\n");
+	if ((sst[2] & 0x03) == 0x03) {
+		rc = do_phonebook_file(EF_MSISDN, "EF_MSISDN", "MSISDN record",
+					outf);
+		if (rc < 0)
+			return(rc);
+	}
+	if ((sst[2] & 0xC0) == 0xC0) {
+		rc = do_smsp_store(outf);
+		if (rc < 0)
+			return(rc);
+	}
+	if ((sst[3] & 0x03) == 0x03) {
+		rc = do_phonebook_file(EF_LND, "EF_LND", "LND cyclic store",
+					outf);
+		if (rc < 0)
+			return(rc);
+	}
+	if ((sst[4] & 0x0C) == 0x0C) {
+		rc = do_phonebook_file(EF_SDN, "EF_SDN", "SDN phonebook", outf);
+		if (rc < 0)
+			return(rc);
+	}
+	if ((sst[13] & 0x03) == 0x03)
+		fprintf(outf, "MBDN present\n");
+	if ((sst[13] & 0x0C) == 0x0C)
+		fprintf(outf, "MWIS present\n");
+	return(0);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/simtool/writecmd.c	Sun Mar 14 07:11:25 2021 +0000
@@ -0,0 +1,129 @@
+#include <sys/types.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.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, 255);
+	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, mode;
+	u_char data[255];
+	int rc;
+
+	if (!strcmp(argv[1], "prev")) {
+		recno = 0;
+		mode = 0x03;
+	} else {
+		recno = strtoul(argv[1], 0, 0);
+		if (recno < 1 || recno > 255) {
+			fprintf(stderr,
+			"error: record number argument is out of range\n");
+			return(-1);
+		}
+		mode = 0x04;
+	}
+	rc = read_hex_data_file(argv[2], data, 255);
+	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, mode, data, curfile_record_len);
+}
+
+cmd_update_rec_imm(argc, argv)
+	char **argv;
+{
+	unsigned recno, mode;
+	u_char data[255];
+	int rc;
+
+	if (!strcmp(argv[1], "prev")) {
+		recno = 0;
+		mode = 0x03;
+	} else {
+		recno = strtoul(argv[1], 0, 0);
+		if (recno < 1 || recno > 255) {
+			fprintf(stderr,
+			"error: record number argument is out of range\n");
+			return(-1);
+		}
+		mode = 0x04;
+	}
+	rc = decode_hex_data_from_string(argv[2], data, 1, 255);
+	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, mode, data, curfile_record_len);
+}
+
+cmd_update_rec_fill(argc, argv)
+	char **argv;
+{
+	unsigned recno, mode, fill_byte;
+	u_char data[255];
+
+	if (!strcmp(argv[1], "prev")) {
+		recno = 0;
+		mode = 0x03;
+	} else {
+		recno = strtoul(argv[1], 0, 0);
+		if (recno < 1 || recno > 255) {
+			fprintf(stderr,
+			"error: record number argument is out of range\n");
+			return(-1);
+		}
+		mode = 0x04;
+	}
+	fill_byte = strtoul(argv[2], 0, 16);
+	if (fill_byte > 0xFF) {
+		fprintf(stderr, "error: invalid fill byte argument\n");
+		return(-1);
+	}
+	memset(data, fill_byte, curfile_record_len);
+	return update_rec_op(recno, mode, data, curfile_record_len);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/simtool/writeops.c	Sun Mar 14 07:11:25 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);
+}