| #!/usr/bin/env python3 |
| |
| # |
| # Utility to deal with sim cards and program the 'magic' ones easily |
| # |
| # |
| # Part of the sim link code of inspired by pySimReader-Serial-src-v2 |
| # |
| # |
| # Copyright (C) 2009 Sylvain Munaut <tnt@246tNt.com> |
| # Copyright (C) 2010 Harald Welte <laforge@gnumonks.org> |
| # |
| # This program is free software: you can redistribute it and/or modify |
| # it under the terms of the GNU General Public License as published by |
| # the Free Software Foundation, either version 2 of the License, or |
| # (at your option) any later version. |
| # |
| # This program is distributed in the hope that it will be useful, |
| # but WITHOUT ANY WARRANTY; without even the implied warranty of |
| # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| # GNU General Public License for more details. |
| # |
| # You should have received a copy of the GNU General Public License |
| # along with this program. If not, see <http://www.gnu.org/licenses/>. |
| # |
| |
| import hashlib |
| from optparse import OptionParser |
| import os |
| import random |
| import re |
| import sys |
| import traceback |
| import json |
| |
| from pySim.commands import SimCardCommands |
| from pySim.transport import init_reader |
| from pySim.cards import _cards_classes, card_detect |
| from pySim.utils import h2b, swap_nibbles, rpad, derive_milenage_opc, calculate_luhn, dec_iccid |
| from pySim.ts_51_011 import EF, EF_AD |
| from pySim.card_handler import * |
| from pySim.utils import * |
| |
| def parse_options(): |
| |
| parser = OptionParser(usage="usage: %prog [options]") |
| |
| parser.add_option("-d", "--device", dest="device", metavar="DEV", |
| help="Serial Device for SIM access [default: %default]", |
| default="/dev/ttyUSB0", |
| ) |
| parser.add_option("-b", "--baud", dest="baudrate", type="int", metavar="BAUD", |
| help="Baudrate used for SIM access [default: %default]", |
| default=9600, |
| ) |
| parser.add_option("-p", "--pcsc-device", dest="pcsc_dev", type='int', metavar="PCSC", |
| help="Which PC/SC reader number for SIM access", |
| default=None, |
| ) |
| parser.add_option("--modem-device", dest="modem_dev", metavar="DEV", |
| help="Serial port of modem for Generic SIM Access (3GPP TS 27.007)", |
| default=None, |
| ) |
| parser.add_option("--modem-baud", dest="modem_baud", type="int", metavar="BAUD", |
| help="Baudrate used for modem's port [default: %default]", |
| default=115200, |
| ) |
| parser.add_option("--osmocon", dest="osmocon_sock", metavar="PATH", |
| help="Socket path for Calypso (e.g. Motorola C1XX) based reader (via OsmocomBB)", |
| default=None, |
| ) |
| parser.add_option("-t", "--type", dest="type", |
| help="Card type (user -t list to view) [default: %default]", |
| default="auto", |
| ) |
| parser.add_option("-T", "--probe", dest="probe", |
| help="Determine card type", |
| default=False, action="store_true" |
| ) |
| parser.add_option("-a", "--pin-adm", dest="pin_adm", |
| help="ADM PIN used for provisioning (overwrites default)", |
| ) |
| parser.add_option("-A", "--pin-adm-hex", dest="pin_adm_hex", |
| help="ADM PIN used for provisioning, as hex string (16 characters long", |
| ) |
| parser.add_option("-e", "--erase", dest="erase", action='store_true', |
| help="Erase beforehand [default: %default]", |
| default=False, |
| ) |
| |
| parser.add_option("-S", "--source", dest="source", |
| help="Data Source[default: %default]", |
| default="cmdline", |
| ) |
| |
| # if mode is "cmdline" |
| parser.add_option("-n", "--name", dest="name", |
| help="Operator name [default: %default]", |
| default="Magic", |
| ) |
| parser.add_option("-c", "--country", dest="country", type="int", metavar="CC", |
| help="Country code [default: %default]", |
| default=1, |
| ) |
| parser.add_option("-x", "--mcc", dest="mcc", type="string", |
| help="Mobile Country Code [default: %default]", |
| default="901", |
| ) |
| parser.add_option("-y", "--mnc", dest="mnc", type="string", |
| help="Mobile Network Code [default: %default]", |
| default="55", |
| ) |
| parser.add_option("--mnclen", dest="mnclen", type="choice", |
| help="Length of Mobile Network Code [default: %default]", |
| default=2, |
| choices=[2, 3], |
| ) |
| parser.add_option("-m", "--smsc", dest="smsc", |
| help="SMSC number (Start with + for international no.) [default: '00 + country code + 5555']", |
| ) |
| parser.add_option("-M", "--smsp", dest="smsp", |
| help="Raw SMSP content in hex [default: auto from SMSC]", |
| ) |
| |
| parser.add_option("-s", "--iccid", dest="iccid", metavar="ID", |
| help="Integrated Circuit Card ID", |
| ) |
| parser.add_option("-i", "--imsi", dest="imsi", |
| help="International Mobile Subscriber Identity", |
| ) |
| parser.add_option("--msisdn", dest="msisdn", |
| help="Mobile Subscriber Integrated Services Digital Number", |
| ) |
| parser.add_option("-k", "--ki", dest="ki", |
| help="Ki (default is to randomize)", |
| ) |
| parser.add_option("-o", "--opc", dest="opc", |
| help="OPC (default is to randomize)", |
| ) |
| parser.add_option("--op", dest="op", |
| help="Set OP to derive OPC from OP and KI", |
| ) |
| parser.add_option("--acc", dest="acc", |
| help="Set ACC bits (Access Control Code). not all card types are supported", |
| ) |
| parser.add_option("--opmode", dest="opmode", type="choice", |
| help="Set UE Operation Mode in EF.AD (Administrative Data)", |
| default=None, |
| choices=['{:02X}'.format(int(m)) for m in EF_AD.OP_MODE], |
| ) |
| parser.add_option("--epdgid", dest="epdgid", |
| help="Set Home Evolved Packet Data Gateway (ePDG) Identifier. (Only FQDN format supported)", |
| ) |
| parser.add_option("--epdgSelection", dest="epdgSelection", |
| help="Set PLMN for ePDG Selection Information. (Only Operator Identifier FQDN format supported)", |
| ) |
| parser.add_option("--pcscf", dest="pcscf", |
| help="Set Proxy Call Session Control Function (P-CSCF) Address. (Only FQDN format supported)", |
| ) |
| parser.add_option("--ims-hdomain", dest="ims_hdomain", |
| help="Set IMS Home Network Domain Name in FQDN format", |
| ) |
| parser.add_option("--impi", dest="impi", |
| help="Set IMS private user identity", |
| ) |
| parser.add_option("--impu", dest="impu", |
| help="Set IMS public user identity", |
| ) |
| parser.add_option("--read-imsi", dest="read_imsi", action="store_true", |
| help="Read the IMSI from the CARD", default=False |
| ) |
| parser.add_option("--read-iccid", dest="read_iccid", action="store_true", |
| help="Read the ICCID from the CARD", default=False |
| ) |
| parser.add_option("-z", "--secret", dest="secret", metavar="STR", |
| help="Secret used for ICCID/IMSI autogen", |
| ) |
| parser.add_option("-j", "--num", dest="num", type=int, |
| help="Card # used for ICCID/IMSI autogen", |
| ) |
| parser.add_option("--batch", dest="batch_mode", |
| help="Enable batch mode [default: %default]", |
| default=False, action='store_true', |
| ) |
| parser.add_option("--batch-state", dest="batch_state", metavar="FILE", |
| help="Optional batch state file", |
| ) |
| |
| # if mode is "csv" |
| parser.add_option("--read-csv", dest="read_csv", metavar="FILE", |
| help="Read parameters from CSV file rather than command line") |
| |
| |
| parser.add_option("--write-csv", dest="write_csv", metavar="FILE", |
| help="Append generated parameters in CSV file", |
| ) |
| parser.add_option("--write-hlr", dest="write_hlr", metavar="FILE", |
| help="Append generated parameters to OpenBSC HLR sqlite3", |
| ) |
| parser.add_option("--dry-run", dest="dry_run", |
| help="Perform a 'dry run', don't actually program the card", |
| default=False, action="store_true") |
| parser.add_option("--card_handler", dest="card_handler", metavar="FILE", |
| help="Use automatic card handling machine") |
| |
| (options, args) = parser.parse_args() |
| |
| if options.type == 'list': |
| for kls in _cards_classes: |
| print(kls.name) |
| sys.exit(0) |
| |
| if options.probe: |
| return options |
| |
| if options.source == 'csv': |
| if (options.imsi is None) and (options.batch_mode is False) and (options.read_imsi is False) and (options.read_iccid is False): |
| parser.error("CSV mode needs either an IMSI, --read-imsi, --read-iccid or batch mode") |
| if options.read_csv is None: |
| parser.error("CSV mode requires a CSV input file") |
| elif options.source == 'cmdline': |
| if ((options.imsi is None) or (options.iccid is None)) and (options.num is None): |
| parser.error("If either IMSI or ICCID isn't specified, num is required") |
| else: |
| parser.error("Only `cmdline' and `csv' sources supported") |
| |
| if (options.read_csv is not None) and (options.source != 'csv'): |
| parser.error("You cannot specify a CSV input file in source != csv") |
| |
| if (options.batch_mode) and (options.num is None): |
| options.num = 0 |
| |
| if (options.batch_mode): |
| if (options.imsi is not None) or (options.iccid is not None): |
| parser.error("Can't give ICCID/IMSI for batch mode, need to use automatic parameters ! see --num and --secret for more informations") |
| |
| if args: |
| parser.error("Extraneous arguments") |
| |
| return options |
| |
| |
| def _digits(secret, usage, len, num): |
| seed = secret + usage + '%d' % num |
| s = hashlib.sha1(seed.encode()) |
| d = ''.join(['%02d' % x for x in s.digest()]) |
| return d[0:len] |
| |
| def _mcc_mnc_digits(mcc, mnc): |
| return '%s%s' % (mcc, mnc) |
| |
| def _cc_digits(cc): |
| return ('%03d' if cc > 100 else '%02d') % cc |
| |
| def _isnum(s, l=-1): |
| return s.isdigit() and ((l== -1) or (len(s) == l)) |
| |
| def _ishex(s, l=-1): |
| hc = '0123456789abcdef' |
| return all([x in hc for x in s.lower()]) and ((l== -1) or (len(s) == l)) |
| |
| |
| def _dbi_binary_quote(s): |
| # Count usage of each char |
| cnt = {} |
| for c in s: |
| cnt[c] = cnt.get(c, 0) + 1 |
| |
| # Find best offset |
| e = 0 |
| m = len(s) |
| for i in range(1, 256): |
| if i == 39: |
| continue |
| sum_ = cnt.get(i, 0) + cnt.get((i+1)&0xff, 0) + cnt.get((i+39)&0xff, 0) |
| if sum_ < m: |
| m = sum_ |
| e = i |
| if m == 0: # No overhead ? use this ! |
| break |
| |
| # Generate output |
| out = [] |
| out.append( chr(e) ) # Offset |
| for c in s: |
| x = (256 + ord(c) - e) % 256 |
| if x in (0, 1, 39): |
| out.append('\x01') |
| out.append(chr(x+1)) |
| else: |
| out.append(chr(x)) |
| |
| return ''.join(out) |
| |
| def gen_parameters(opts): |
| """Generates Name, ICCID, MCC, MNC, IMSI, SMSP, Ki, PIN-ADM from the |
| options given by the user""" |
| |
| # MCC/MNC |
| mcc = opts.mcc |
| mnc = opts.mnc |
| |
| if not mcc.isdigit() or not mnc.isdigit(): |
| raise ValueError('mcc & mnc must only contain decimal digits') |
| if len(mcc) < 1 or len(mcc) > 3: |
| raise ValueError('mcc must be between 1 .. 3 digits') |
| if len(mnc) < 1 or len(mnc) > 3: |
| raise ValueError('mnc must be between 1 .. 3 digits') |
| |
| # MCC always has 3 digits |
| mcc = lpad(mcc, 3, "0") |
| # MNC must be at least 2 digits |
| mnc = lpad(mnc, 2, "0") |
| |
| # Digitize country code (2 or 3 digits) |
| cc_digits = _cc_digits(opts.country) |
| |
| # Digitize MCC/MNC (5 or 6 digits) |
| plmn_digits = _mcc_mnc_digits(mcc, mnc) |
| |
| if opts.name is not None: |
| if len(opts.name) > 16: |
| raise ValueError('Service Provider Name must max 16 characters!') |
| |
| if opts.msisdn is not None: |
| msisdn = opts.msisdn |
| if msisdn[0] == '+': |
| msisdn = msisdn[1:] |
| if not msisdn.isdigit(): |
| raise ValueError('MSISDN must be digits only! ' |
| 'Start with \'+\' for international numbers.') |
| if len(msisdn) > 10 * 2: |
| # TODO: Support MSISDN of length > 20 (10 Bytes) |
| raise ValueError('MSISDNs longer than 20 digits are not (yet) supported.') |
| |
| # ICCID (19 digits, E.118), though some phase1 vendors use 20 :( |
| if opts.iccid is not None: |
| iccid = opts.iccid |
| if not _isnum(iccid, 19) and not _isnum(iccid, 20): |
| raise ValueError('ICCID must be 19 or 20 digits !') |
| |
| else: |
| if opts.num is None: |
| raise ValueError('Neither ICCID nor card number specified !') |
| |
| iccid = ( |
| '89' + # Common prefix (telecom) |
| cc_digits + # Country Code on 2/3 digits |
| plmn_digits # MCC/MNC on 5/6 digits |
| ) |
| |
| ml = 18 - len(iccid) |
| |
| if opts.secret is None: |
| # The raw number |
| iccid += ('%%0%dd' % ml) % opts.num |
| else: |
| # Randomized digits |
| iccid += _digits(opts.secret, 'ccid', ml, opts.num) |
| |
| # Add checksum digit |
| iccid += ('%1d' % calculate_luhn(iccid)) |
| |
| # IMSI (15 digits usually) |
| if opts.imsi is not None: |
| imsi = opts.imsi |
| if not _isnum(imsi): |
| raise ValueError('IMSI must be digits only !') |
| |
| else: |
| if opts.num is None: |
| raise ValueError('Neither IMSI nor card number specified !') |
| |
| ml = 15 - len(plmn_digits) |
| |
| if opts.secret is None: |
| # The raw number |
| msin = ('%%0%dd' % ml) % opts.num |
| else: |
| # Randomized digits |
| msin = _digits(opts.secret, 'imsi', ml, opts.num) |
| |
| imsi = ( |
| plmn_digits + # MCC/MNC on 5/6 digits |
| msin # MSIN |
| ) |
| |
| # SMSP |
| if opts.smsp is not None: |
| smsp = opts.smsp |
| if not _ishex(smsp): |
| raise ValueError('SMSP must be hex digits only !') |
| if len(smsp) < 28*2: |
| raise ValueError('SMSP must be at least 28 bytes') |
| |
| else: |
| ton = "81" |
| if opts.smsc is not None: |
| smsc = opts.smsc |
| if smsc[0] == '+': |
| ton = "91" |
| smsc = smsc[1:] |
| if not _isnum(smsc): |
| raise ValueError('SMSC must be digits only!\n \ |
| Start with \'+\' for international numbers') |
| else: |
| smsc = '00%d' % opts.country + '5555' # Hack ... |
| |
| smsc = '%02d' % ((len(smsc) + 3)//2,) + ton + swap_nibbles(rpad(smsc, 20)) |
| |
| smsp = ( |
| 'e1' + # Parameters indicator |
| 'ff' * 12 + # TP-Destination address |
| smsc + # TP-Service Centre Address |
| '00' + # TP-Protocol identifier |
| '00' + # TP-Data coding scheme |
| '00' # TP-Validity period |
| ) |
| |
| # ACC |
| if opts.acc is not None: |
| acc = opts.acc |
| if not _ishex(acc): |
| raise ValueError('ACC must be hex digits only !') |
| if len(acc) != 2*2: |
| raise ValueError('ACC must be exactly 2 bytes') |
| |
| else: |
| acc = None |
| |
| # Ki (random) |
| if opts.ki is not None: |
| ki = opts.ki |
| if not re.match('^[0-9a-fA-F]{32}$', ki): |
| raise ValueError('Ki needs to be 128 bits, in hex format') |
| else: |
| ki = ''.join(['%02x' % random.randrange(0,256) for i in range(16)]) |
| |
| # OPC (random) |
| if opts.opc is not None: |
| opc = opts.opc |
| if not re.match('^[0-9a-fA-F]{32}$', opc): |
| raise ValueError('OPC needs to be 128 bits, in hex format') |
| |
| elif opts.op is not None: |
| opc = derive_milenage_opc(ki, opts.op) |
| else: |
| opc = ''.join(['%02x' % random.randrange(0,256) for i in range(16)]) |
| |
| pin_adm = sanitize_pin_adm(opts.pin_adm, opts.pin_adm_hex) |
| |
| # ePDG Selection Information |
| if opts.epdgSelection: |
| if len(opts.epdgSelection) < 5 or len(opts.epdgSelection) > 6: |
| raise ValueError('ePDG Selection Information is not valid') |
| epdg_mcc = opts.epdgSelection[:3] |
| epdg_mnc = opts.epdgSelection[3:] |
| if not epdg_mcc.isdigit() or not epdg_mnc.isdigit(): |
| raise ValueError('PLMN for ePDG Selection must only contain decimal digits') |
| |
| # Return that |
| return { |
| 'name' : opts.name, |
| 'iccid' : iccid, |
| 'mcc' : mcc, |
| 'mnc' : mnc, |
| 'imsi' : imsi, |
| 'smsp' : smsp, |
| 'ki' : ki, |
| 'opc' : opc, |
| 'acc' : acc, |
| 'pin_adm' : pin_adm, |
| 'msisdn' : opts.msisdn, |
| 'epdgid' : opts.epdgid, |
| 'epdgSelection' : opts.epdgSelection, |
| 'pcscf' : opts.pcscf, |
| 'ims_hdomain': opts.ims_hdomain, |
| 'impi' : opts.impi, |
| 'impu' : opts.impu, |
| 'opmode': opts.opmode, |
| } |
| |
| |
| def print_parameters(params): |
| |
| s = ["Generated card parameters :"] |
| if 'name' in params: |
| s.append(" > Name : %(name)s") |
| if 'smsp' in params: |
| s.append(" > SMSP : %(smsp)s") |
| s.append(" > ICCID : %(iccid)s") |
| s.append(" > MCC/MNC : %(mcc)s/%(mnc)s") |
| s.append(" > IMSI : %(imsi)s") |
| s.append(" > Ki : %(ki)s") |
| s.append(" > OPC : %(opc)s") |
| if 'acc' in params: |
| s.append(" > ACC : %(acc)s") |
| s.append(" > ADM1(hex): %(pin_adm)s") |
| if 'opmode' in params: |
| s.append(" > OPMODE : %(opmode)s") |
| print("\n".join(s) % params) |
| |
| |
| def write_params_csv(opts, params): |
| # csv |
| if opts.write_csv: |
| import csv |
| row = ['name', 'iccid', 'mcc', 'mnc', 'imsi', 'smsp', 'ki', 'opc'] |
| f = open(opts.write_csv, 'a') |
| cw = csv.writer(f) |
| cw.writerow([params[x] for x in row]) |
| f.close() |
| |
| def _read_params_csv(opts, iccid=None, imsi=None): |
| import csv |
| f = open(opts.read_csv, 'r') |
| cr = csv.DictReader(f) |
| |
| # Lower-case fieldnames |
| cr.fieldnames = [ field.lower() for field in cr.fieldnames ] |
| |
| i = 0 |
| if not 'iccid' in cr.fieldnames: |
| raise Exception("CSV file in wrong format!") |
| for row in cr: |
| if opts.num is not None and opts.read_iccid is False and opts.read_imsi is False: |
| if opts.num == i: |
| f.close() |
| return row |
| i += 1 |
| if row['iccid'] == iccid: |
| f.close() |
| return row |
| |
| if row['imsi'] == imsi: |
| f.close() |
| return row |
| |
| f.close() |
| return None |
| |
| def read_params_csv(opts, imsi=None, iccid=None): |
| row = _read_params_csv(opts, iccid=iccid, imsi=imsi) |
| if row is not None: |
| row['mcc'] = row.get('mcc', mcc_from_imsi(row.get('imsi'))) |
| row['mnc'] = row.get('mnc', mnc_from_imsi(row.get('imsi'))) |
| |
| pin_adm = None |
| # We need to escape the pin_adm we get from the csv |
| if 'pin_adm' in row: |
| pin_adm = ''.join(['%02x'%(ord(x)) for x in row['pin_adm']]) |
| # Stay compatible to the odoo csv format |
| elif 'adm1' in row: |
| pin_adm = ''.join(['%02x'%(ord(x)) for x in row['adm1']]) |
| if pin_adm: |
| row['pin_adm'] = rpad(pin_adm, 16) |
| |
| # If the CSV-File defines a pin_adm_hex field use this field to |
| # generate pin_adm from that. |
| pin_adm_hex = row.get('pin_adm_hex') |
| if pin_adm_hex: |
| if len(pin_adm_hex) == 16: |
| row['pin_adm'] = pin_adm_hex |
| # Ensure that it's hex-encoded |
| try: |
| try_encode = h2b(pin_adm) |
| except ValueError: |
| raise ValueError("pin_adm_hex needs to be hex encoded using this option") |
| else: |
| raise ValueError("pin_adm_hex needs to be exactly 16 digits (hex encoded)") |
| |
| return row |
| |
| |
| def write_params_hlr(opts, params): |
| # SQLite3 OpenBSC HLR |
| if opts.write_hlr: |
| import sqlite3 |
| conn = sqlite3.connect(opts.write_hlr) |
| |
| c = conn.execute( |
| 'INSERT INTO Subscriber ' + |
| '(imsi, name, extension, authorized, created, updated) ' + |
| 'VALUES ' + |
| '(?,?,?,1,datetime(\'now\'),datetime(\'now\'));', |
| [ |
| params['imsi'], |
| params['name'], |
| '9' + params['iccid'][-5:-1] |
| ], |
| ) |
| sub_id = c.lastrowid |
| c.close() |
| |
| c = conn.execute( |
| 'INSERT INTO AuthKeys ' + |
| '(subscriber_id, algorithm_id, a3a8_ki)' + |
| 'VALUES ' + |
| '(?,?,?)', |
| [ sub_id, 2, sqlite3.Binary(_dbi_binary_quote(h2b(params['ki']))) ], |
| ) |
| |
| conn.commit() |
| conn.close() |
| |
| def write_parameters(opts, params): |
| write_params_csv(opts, params) |
| write_params_hlr(opts, params) |
| |
| |
| BATCH_STATE = [ 'name', 'country', 'mcc', 'mnc', 'smsp', 'secret', 'num' ] |
| BATCH_INCOMPATIBLE = ['iccid', 'imsi', 'ki'] |
| |
| def init_batch(opts): |
| # Need to do something ? |
| if not opts.batch_mode: |
| return |
| |
| for k in BATCH_INCOMPATIBLE: |
| if getattr(opts, k): |
| print("Incompatible option with batch_state: %s" % (k,)) |
| sys.exit(-1) |
| |
| # Don't load state if there is none ... |
| if not opts.batch_state: |
| return |
| |
| if not os.path.isfile(opts.batch_state): |
| print("No state file yet") |
| return |
| |
| # Get stored data |
| fh = open(opts.batch_state) |
| d = json.loads(fh.read()) |
| fh.close() |
| |
| for k,v in d.iteritems(): |
| setattr(opts, k, v) |
| |
| |
| def save_batch(opts): |
| # Need to do something ? |
| if not opts.batch_mode or not opts.batch_state: |
| return |
| |
| d = json.dumps(dict([(k,getattr(opts,k)) for k in BATCH_STATE])) |
| fh = open(opts.batch_state, 'w') |
| fh.write(d) |
| fh.close() |
| |
| |
| def process_card(opts, first, card_handler): |
| |
| if opts.dry_run is False: |
| # Connect transport |
| card_handler.get(first) |
| |
| if opts.dry_run is False: |
| # Get card |
| card = card_detect(opts.type, scc) |
| if card is None: |
| print("No card detected!") |
| return -1 |
| |
| # Probe only |
| if opts.probe: |
| return 0 |
| |
| # Erase if requested |
| if opts.erase: |
| print("Formatting ...") |
| card.erase() |
| card.reset() |
| |
| # Generate parameters |
| if opts.source == 'cmdline': |
| cp = gen_parameters(opts) |
| elif opts.source == 'csv': |
| imsi = None |
| iccid = None |
| if opts.read_iccid: |
| if opts.dry_run: |
| # Connect transport |
| card_handler.get(False) |
| (res,_) = scc.read_binary(['3f00', '2fe2'], length=10) |
| iccid = dec_iccid(res) |
| elif opts.read_imsi: |
| if opts.dry_run: |
| # Connect transport |
| card_handler.get(False) |
| (res,_) = scc.read_binary(EF['IMSI']) |
| imsi = swap_nibbles(res)[3:] |
| else: |
| imsi = opts.imsi |
| cp = read_params_csv(opts, imsi=imsi, iccid=iccid) |
| if cp is None: |
| print("Error reading parameters from CSV file!\n") |
| return 2 |
| print_parameters(cp) |
| |
| if opts.dry_run is False: |
| # Program the card |
| print("Programming ...") |
| card.program(cp) |
| else: |
| print("Dry Run: NOT PROGRAMMING!") |
| |
| # Write parameters permanently |
| write_parameters(opts, cp) |
| |
| # Batch mode state update and save |
| if opts.num is not None: |
| opts.num += 1 |
| save_batch(opts) |
| |
| card_handler.done() |
| return 0 |
| |
| |
| if __name__ == '__main__': |
| |
| # Parse options |
| opts = parse_options() |
| |
| # Init card reader driver |
| sl = init_reader(opts) |
| if sl is None: |
| exit(1) |
| |
| # Create command layer |
| scc = SimCardCommands(transport=sl) |
| |
| # If we use a CSV file as data input, check if the CSV file exists. |
| if opts.source == 'csv': |
| print("Using CSV file as data input: " + str(opts.read_csv)) |
| if not os.path.isfile(opts.read_csv): |
| print("CSV file not found!") |
| sys.exit(1) |
| |
| # Batch mode init |
| init_batch(opts) |
| |
| if opts.card_handler: |
| card_handler = card_handler_auto(sl, opts.card_handler) |
| else: |
| card_handler = card_handler(sl) |
| |
| # Iterate |
| first = True |
| card = None |
| |
| while 1: |
| try: |
| rc = process_card(opts, first, card_handler) |
| except (KeyboardInterrupt): |
| print("") |
| print("Terminated by user!") |
| sys.exit(0) |
| except (SystemExit): |
| raise |
| except: |
| print("") |
| print("Card programming failed with an execption:") |
| print("---------------------8<---------------------") |
| traceback.print_exc() |
| print("---------------------8<---------------------") |
| print("") |
| rc = -1 |
| |
| # Something did not work as well as expected, however, lets |
| # make sure the card is pulled from the reader. |
| if rc != 0: |
| card_handler.error() |
| |
| # If we are not in batch mode we are done in any case, so lets |
| # exit here. |
| if not opts.batch_mode: |
| sys.exit(rc) |
| |
| first = False |