view simtool/pbupdate.c @ 124:6c4567dd8946

fc-simtool: add non-interactive one-shot command (or script) mode
author Mychaela Falconia <falcon@freecalypso.org>
date Thu, 28 Jan 2021 18:56:34 +0000
parents 70219d92c32e
children f18b87115cca
line wrap: on
line source

/*
 * This module implements the pb-update command.
 */

#include <sys/types.h>
#include <ctype.h>
#include <string.h>
#include <strings.h>
#include <stdio.h>
#include <stdlib.h>
#include <pcsclite.h>
#include <winscard.h>
#include "globals.h"

static u_char gsm7_encode_table[256] = {
	0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,	/* 0x00 */
	0xFF, 0xFF, '\n', 0xFF, 0xFF, '\r', 0xFF, 0xFF,
	0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,	/* 0x10 */
	0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
	' ',  '!',  '"',  '#',  0x02, '%',  '&',  0x27,	/* 0x20 */
	'(',  ')',  '*',  '+',  ',',  '-',  '.',  '/',
	'0',  '1',  '2',  '3',  '4',  '5',  '6',  '7',	/* 0x30 */
	'8',  '9',  ':',  ';',  '<',  '=',  '>',  '?',
	0x00, 'A',  'B',  'C',  'D',  'E',  'F',  'G',	/* 0x40 */
	'H',  'I',  'J',  'K',  'L',  'M',  'N',  'O',
	'P',  'Q',  'R',  'S',  'T',  'U',  'V',  'W',	/* 0x50 */
	'X',  'Y',  'Z',  0xBC, 0xAF, 0xBE, 0x94, 0x11,
	0xFF, 'a',  'b',  'c',  'd',  'e',  'f',  'g',	/* 0x60 */
	'h',  'i',  'j',  'k',  'l',  'm',  'n',  'o',
	'p',  'q',  'r',  's',  't',  'u',  'v',  'w',	/* 0x70 */
	'x',  'y',  'z',  0xA8, 0xC0, 0xA9, 0xBD, 0xFF,
	0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,	/* 0x80 */
	0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
	0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,	/* 0x90 */
	0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
	0xFF, 0x40, 0xFF, 0x01, 0x24, 0x03, 0xFF, 0x5F,	/* 0xA0 */
	0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
	0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,	/* 0xB0 */
	0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x60,
	0xFF, 0xFF, 0xFF, 0xFF, 0x5B, 0x0E, 0x1C, 0x09,	/* 0xC0 */
	0xFF, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
	0xFF, 0x5D, 0xFF, 0xFF, 0xFF, 0xFF, 0x5C, 0xFF,	/* 0xD0 */
	0x0B, 0xFF, 0xFF, 0xFF, 0x5E, 0xFF, 0xFF, 0x1E,
	0x7F, 0xFF, 0xFF, 0xFF, 0x7B, 0x0F, 0x1D, 0xFF,	/* 0xE0 */
	0x04, 0x05, 0xFF, 0xFF, 0x07, 0xFF, 0xFF, 0xFF,
	0xFF, 0x7D, 0x08, 0xFF, 0xFF, 0xFF, 0x7C, 0xFF,	/* 0xF0 */
	0x0C, 0x06, 0xFF, 0xFF, 0x7E, 0xFF, 0xFF, 0xFF
};

static
digit_char_to_gsm(ch)
{
	switch (ch) {
	case '0':
	case '1':
	case '2':
	case '3':
	case '4':
	case '5':
	case '6':
	case '7':
	case '8':
	case '9':
		return (ch - '0');
	case '*':
		return 0xA;
	case '#':
		return 0xB;
	case 'a':
	case 'b':
	case 'c':
		return (ch - 'a' + 0xC);
	case 'A':
	case 'B':
	case 'C':
		return (ch - 'A' + 0xC);
	}
	return (-1);
}

static void
pack_digit_bytes(digits, dest, num_digit_bytes)
	u_char *digits, *dest;
	unsigned num_digit_bytes;
{
	u_char *sp, *dp;
	unsigned n;

	sp = digits;
	dp = dest;
	for (n = 0; n < num_digit_bytes; n++) {
		*dp++ = sp[0] | (sp[1] << 4);
		sp += 2;
	}
}

static char *
decode_qstring_alpha(cp, record, filename_for_errs, lineno_for_errs)
	char *cp, *filename_for_errs;
	u_char *record;
{
	unsigned maxlen, acclen, nadd;
	int c;

	maxlen = curfile_record_len - 14;
	for (acclen = 0; ; ) {
		if (*cp == '\0') {
unterm_qstring:		fprintf(stderr,
				"%s line %d: unterminated quoted string\n",
				filename_for_errs, lineno_for_errs);
			return(0);
		}
		if (*cp == '"')
			break;
		c = *cp++;
		if (c == '\\') {
			if (*cp == '\0')
				goto unterm_qstring;
			c = *cp++;
			switch (c) {
			case 'n':
				c = '\n';
				break;
			case 'r':
				c = '\r';
				break;
			case '"':
			case '\\':
				break;
			default:
				fprintf(stderr,
				"%s line %d: non-understood backslash escape\n",
					filename_for_errs, lineno_for_errs);
				return(0);
			}
		}
		c = gsm7_encode_table[c];
		if (c == 0xFF) {
			fprintf(stderr,
	"%s line %d: character in quoted string cannot be encoded in GSM7\n",
				filename_for_errs, lineno_for_errs);
			return(0);
		}
		if (c & 0x80)
			nadd = 2;
		else
			nadd = 1;
		if (acclen + nadd > maxlen) {
			fprintf(stderr,
		"%s line %d: alpha tag string is longer than SIM limit\n",
				filename_for_errs, lineno_for_errs);
			return(0);
		}
		if (c & 0x80)
			record[acclen++] = 0x1B;
		record[acclen++] = c & 0x7F;
	}
	return(cp + 1);
}

static char *
decode_hex_alpha(cp, record, filename_for_errs, lineno_for_errs)
	char *cp, *filename_for_errs;
	u_char *record;
{
	unsigned maxlen, acclen;

	maxlen = curfile_record_len - 14;
	for (acclen = 0; ; ) {
		if (!isxdigit(cp[0]) || !isxdigit(cp[1]))
			break;
		if (acclen >= maxlen) {
			fprintf(stderr,
		"%s line %d: alpha tag string is longer than SIM limit\n",
				filename_for_errs, lineno_for_errs);
			return(0);
		}
		record[acclen++] = (decode_hex_digit(cp[0]) << 4) |
				    decode_hex_digit(cp[1]);
		cp += 2;
	}
	return(cp);
}

static
process_record(line, filename_for_errs, lineno_for_errs)
	char *line, *filename_for_errs;
{
	unsigned recno;
	u_char record[255], *fixp;
	u_char digits[20];
	unsigned ndigits, num_digit_bytes;
	char *cp;
	int c;

	recno = strtoul(line+1, 0, 10);
	if (recno < 1 || recno > curfile_record_count) {
		fprintf(stderr, "%s line %d: record number is out of range\n",
			filename_for_errs, lineno_for_errs);
		return(-1);
	}
	cp = line + 1;
	while (isdigit(*cp))
		cp++;
	if (*cp++ != ':') {
inv_syntax:	fprintf(stderr, "%s line %d: invalid syntax\n",
			filename_for_errs, lineno_for_errs);
		return(-1);
	}
	while (isspace(*cp))
		cp++;
	memset(record, 0xFF, curfile_record_len);
	fixp = record + curfile_record_len - 14;
	if (digit_char_to_gsm(*cp) < 0)
		goto inv_syntax;
	for (ndigits = 0; ; ndigits++) {
		c = digit_char_to_gsm(*cp);
		if (c < 0)
			break;
		cp++;
		if (ndigits >= 20) {
			fprintf(stderr, "%s line %d: too many number digits\n",
				filename_for_errs, lineno_for_errs);
			return(-1);
		}
		digits[ndigits] = c;
	}
	if (ndigits & 1)
		digits[ndigits++] = 0xF;
	num_digit_bytes = ndigits >> 1;
	fixp[0] = num_digit_bytes + 1;
	pack_digit_bytes(digits, fixp + 2, num_digit_bytes);
	if (*cp++ != ',')
		goto inv_syntax;
	if (cp[0] != '0' || cp[1] != 'x' && cp[1] != 'X' || !isxdigit(cp[2]) ||
	    !isxdigit(cp[3]) || !isspace(cp[4]))
		goto inv_syntax;
	fixp[1] = strtoul(cp, 0, 16);
	cp += 5;
	while (isspace(*cp))
		cp++;
	if (!strncasecmp(cp, "CCP=", 4)) {
		cp += 4;
		fixp[12] = strtoul(cp, 0, 0);
		while (*cp && !isspace(*cp))
			cp++;
		while (isspace(*cp))
			cp++;
	}
	if (!strncasecmp(cp, "EXT=", 4)) {
		cp += 4;
		fixp[13] = strtoul(cp, 0, 0);
		while (*cp && !isspace(*cp))
			cp++;
		while (isspace(*cp))
			cp++;
	}
	if (*cp == '"') {
		cp++;
		cp = decode_qstring_alpha(cp, record, filename_for_errs,
					  lineno_for_errs);
		if (!cp)
			return(-1);
	} else if (!strncasecmp(cp, "HEX", 3)) {
		cp += 3;
		while (isspace(*cp))
			cp++;
		cp = decode_hex_alpha(cp, record, filename_for_errs,
				      lineno_for_errs);
		if (!cp)
			return(-1);
	} else
		goto inv_syntax;
	while (isspace(*cp))
		cp++;
	if (*cp)
		goto inv_syntax;
	return update_rec_op(recno, 0x04, record, curfile_record_len);
}

cmd_pb_update(argc, argv)
	char **argv;
{
	int rc;
	FILE *inf;
	int lineno;
	char linebuf[1024];

	rc = phonebook_op_common(argv[1]);
	if (rc < 0)
		return(rc);
	inf = fopen(argv[2], "r");
	if (!inf) {
		perror(argv[2]);
		return(-1);
	}
	for (lineno = 1; fgets(linebuf, sizeof linebuf, inf); lineno++) {
		if (!index(linebuf, '\n')) {
			fprintf(stderr,
				"%s line %d: too long or missing newline\n",
				argv[2], lineno);
			fclose(inf);
			return(-1);
		}
		if (linebuf[0] != '#' || !isdigit(linebuf[1]))
			continue;
		rc = process_record(linebuf, argv[2], lineno);
		if (rc < 0) {
			fclose(inf);
			return(rc);
		}
	}
	fclose(inf);
	return(0);
}

static
decode_number_arg(arg, fixp)
	char *arg;
	u_char *fixp;
{
	u_char digits[20];
	unsigned ndigits, num_digit_bytes;
	char *cp, *endp;
	int c;

	cp = arg;
	if (*cp == '+') {
		fixp[1] = 0x91;
		cp++;
	} else
		fixp[1] = 0x81;
	if (digit_char_to_gsm(*cp) < 0) {
inv_arg:	fprintf(stderr, "error: invalid phone number argument\n");
		return(-1);
	}
	for (ndigits = 0; ; ndigits++) {
		c = digit_char_to_gsm(*cp);
		if (c < 0)
			break;
		cp++;
		if (ndigits >= 20) {
			fprintf(stderr, "error: too many number digits\n");
			return(-1);
		}
		digits[ndigits] = c;
	}
	if (ndigits & 1)
		digits[ndigits++] = 0xF;
	num_digit_bytes = ndigits >> 1;
	fixp[0] = num_digit_bytes + 1;
	pack_digit_bytes(digits, fixp + 2, num_digit_bytes);
	if (*cp == ',') {
		cp++;
		if (!isdigit(*cp))
			goto inv_arg;
		fixp[1] = strtoul(cp, &endp, 0);
		if (*endp)
			goto inv_arg;
	} else if (*cp)
		goto inv_arg;
	return(0);
}

static
decode_alphatag_arg(arg, record)
	char *arg;
	u_char *record;
{
	unsigned maxlen, acclen, nadd;
	char *cp;
	int c;

	maxlen = curfile_record_len - 14;
	cp = arg;
	for (acclen = 0; *cp; ) {
		c = *cp++;
		if (c == '\\') {
			if (*cp == '\0') {
				fprintf(stderr,
					"error: danging backslash escape\n");
				return(-1);
			}
			c = *cp++;
			switch (c) {
			case 'n':
				c = '\n';
				break;
			case 'r':
				c = '\r';
				break;
			case '"':
			case '\\':
				break;
			default:
				fprintf(stderr,
				"error: non-understood backslash escape\n");
				return(-1);
			}
		}
		c = gsm7_encode_table[c];
		if (c == 0xFF) {
			fprintf(stderr,
	"error: character in alpha tag string cannot be encoded in GSM7\n");
			return(-1);
		}
		if (c & 0x80)
			nadd = 2;
		else
			nadd = 1;
		if (acclen + nadd > maxlen) {
			fprintf(stderr,
			"error: alpha tag string is longer than SIM limit\n");
			return(-1);
		}
		if (c & 0x80)
			record[acclen++] = 0x1B;
		record[acclen++] = c & 0x7F;
	}
	return(0);
}

cmd_pb_update_imm(argc, argv)
	char **argv;
{
	int rc;
	unsigned recno;
	u_char record[255], *fixp;

	rc = phonebook_op_common(argv[1]);
	if (rc < 0)
		return(rc);
	recno = strtoul(argv[2], 0, 0);
	if (recno < 1 || recno > curfile_record_count) {
		fprintf(stderr, "error: specified record number is invalid\n");
		return(-1);
	}
	memset(record, 0xFF, curfile_record_len);
	fixp = record + curfile_record_len - 14;
	rc = decode_number_arg(argv[3], fixp);
	if (rc < 0)
		return(rc);
	if (argv[4]) {
		rc = decode_alphatag_arg(argv[4], record);
		if (rc < 0)
			return(rc);
	}
	return update_rec_op(recno, 0x04, record, curfile_record_len);
}

static
decode_alphatag_arg_hex(arg, record)
	char *arg;
	u_char *record;
{
	unsigned maxlen, acclen;

	maxlen = curfile_record_len - 14;
	for (acclen = 0; ; acclen++) {
		while (isspace(*arg))
			arg++;
		if (!*arg)
			break;
		if (!isxdigit(arg[0]) || !isxdigit(arg[1])) {
			fprintf(stderr, "error: invalid hex string input\n");
			return(-1);
		}
		if (acclen >= maxlen) {
			fprintf(stderr,
			"error: alpha tag string is longer than SIM limit\n");
			return(-1);
		}
		record[acclen] = (decode_hex_digit(arg[0]) << 4) |
				 decode_hex_digit(arg[1]);
		arg += 2;
	}
	return(0);
}

cmd_pb_update_imm_hex(argc, argv)
	char **argv;
{
	int rc;
	unsigned recno;
	u_char record[255], *fixp;

	rc = phonebook_op_common(argv[1]);
	if (rc < 0)
		return(rc);
	recno = strtoul(argv[2], 0, 0);
	if (recno < 1 || recno > curfile_record_count) {
		fprintf(stderr, "error: specified record number is invalid\n");
		return(-1);
	}
	memset(record, 0xFF, curfile_record_len);
	fixp = record + curfile_record_len - 14;
	rc = decode_number_arg(argv[3], fixp);
	if (rc < 0)
		return(rc);
	rc = decode_alphatag_arg_hex(argv[4], record);
	if (rc < 0)
		return(rc);
	return update_rec_op(recno, 0x04, record, curfile_record_len);
}