aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rwxr-xr-xpySim-shell.py94
-rw-r--r--pySim/filesystem.py5
2 files changed, 80 insertions, 19 deletions
diff --git a/pySim-shell.py b/pySim-shell.py
index 5aa311c..a57aa66 100755
--- a/pySim-shell.py
+++ b/pySim-shell.py
@@ -20,6 +20,7 @@
from typing import List
import json
+import traceback
import cmd2
from cmd2 import style, fg, bg
@@ -99,15 +100,12 @@ def init_card(sl):
class PysimApp(cmd2.Cmd):
CUSTOM_CATEGORY = 'pySim Commands'
def __init__(self, card, rs, script = None):
- basic_commands = [Iso7816Commands(), PySimCommands()]
super().__init__(persistent_history_file='~/.pysim_shell_history', allow_cli_args=False,
- use_ipython=True, auto_load_commands=False, command_sets=basic_commands, startup_script=script)
+ use_ipython=True, auto_load_commands=False, startup_script=script)
self.intro = style('Welcome to pySim-shell!', fg=fg.red)
self.default_category = 'pySim-shell built-in commands'
- self.card = card
- iccid, sw = self.card.read_iccid()
- self.iccid = iccid
- self.rs = rs
+ self.card = None
+ self.rs = None
self.py_locals = { 'card': self.card, 'rs' : self.rs }
self.numeric_path = False
self.add_settable(cmd2.Settable('numeric_path', bool, 'Print File IDs instead of names',
@@ -115,13 +113,47 @@ class PysimApp(cmd2.Cmd):
self.conserve_write = True
self.add_settable(cmd2.Settable('conserve_write', bool, 'Read and compare before write',
onchange_cb=self._onchange_conserve_write))
- self.update_prompt()
self.json_pretty_print = True
self.add_settable(cmd2.Settable('json_pretty_print', bool, 'Pretty-Print JSON output'))
self.apdu_trace = False
self.add_settable(cmd2.Settable('apdu_trace', bool, 'Trace and display APDUs exchanged with card',
onchange_cb=self._onchange_apdu_trace))
+ self.equip(card, rs)
+
+ def equip(self, card, rs):
+ """
+ Equip pySim-shell with the supplied card and runtime state, add (or remove) all required settables and
+ and commands to enable card operations.
+ """
+
+ # Unequip everything from pySim-shell that would not work in unequipped state
+ if self.rs:
+ self.rs.unregister_cmds(self)
+ cmd_set = self.find_commandsets(Iso7816Commands)
+ if cmd_set:
+ self.unregister_command_set(cmd_set[0])
+ cmd_set = self.find_commandsets(PySimCommands)
+ if cmd_set:
+ self.unregister_command_set(cmd_set[0])
+
+ self.card = card
+ self.rs = rs
+
+ # When a card object and a runtime state is present, (re)equip pySim-shell with everything that is
+ # needed to operate on cards.
+ if self.card and self.rs:
+ self._onchange_conserve_write('conserve_write', False, self.conserve_write)
+ self._onchange_apdu_trace('apdu_trace', False, self.apdu_trace)
+ self.register_command_set(Iso7816Commands())
+ self.register_command_set(PySimCommands())
+ self.iccid, sw = self.card.read_iccid()
+ rs.select('MF', self)
+ else:
+ self.poutput("pySim-shell not equipped!")
+
+ self.update_prompt()
+
def poutput_json(self, data, force_no_pretty = False):
"""like cmd2.poutput() but for a JSON serializable dict."""
if force_no_pretty or self.json_pretty_print == False:
@@ -134,13 +166,15 @@ class PysimApp(cmd2.Cmd):
self.update_prompt()
def _onchange_conserve_write(self, param_name, old, new):
- self.rs.conserve_write = new
+ if self.rs:
+ self.rs.conserve_write = new
def _onchange_apdu_trace(self, param_name, old, new):
- if new == True:
- self.card._scc._tp.apdu_tracer = self.Cmd2ApduTracer(self)
- else:
- self.card._scc._tp.apdu_tracer = None
+ if self.card:
+ if new == True:
+ self.card._scc._tp.apdu_tracer = self.Cmd2ApduTracer(self)
+ else:
+ self.card._scc._tp.apdu_tracer = None
class Cmd2ApduTracer(ApduTracer):
def __init__(self, cmd2_app):
@@ -151,14 +185,23 @@ class PysimApp(cmd2.Cmd):
self.cmd2.poutput("<- %s: %s" % (sw, resp))
def update_prompt(self):
- path_list = self.rs.selected_file.fully_qualified_path(not self.numeric_path)
- self.prompt = 'pySIM-shell (%s)> ' % ('/'.join(path_list))
+ if self.rs:
+ path_list = self.rs.selected_file.fully_qualified_path(not self.numeric_path)
+ self.prompt = 'pySIM-shell (%s)> ' % ('/'.join(path_list))
+ else:
+ self.prompt = 'pySIM-shell (no card)> '
@cmd2.with_category(CUSTOM_CATEGORY)
def do_intro(self, _):
"""Display the intro banner"""
self.poutput(self.intro)
+ @cmd2.with_category(CUSTOM_CATEGORY)
+ def do_equip(self, opts):
+ """Equip pySim-shell with card"""
+ rs, card = init_card(sl);
+ self.equip(card, rs)
+
@with_default_category('pySim Commands')
class PySimCommands(CommandSet):
@@ -538,16 +581,29 @@ if __name__ == '__main__':
# Create command layer
scc = SimCardCommands(transport=sl)
- rs, card = init_card(sl)
- if (rs is None or card is None):
- exit(1)
- app = PysimApp(card, rs, opts.script)
- rs.select('MF', app)
+ # Detect and initialize the card in the reader. This may fail when there
+ # is no card in the reader or the card is unresponsive. PysimApp is
+ # able to tolerate and recover from that.
+ try:
+ rs, card = init_card(sl)
+ app = PysimApp(card, rs, opts.script)
+ except:
+ print("Card initialization failed with an exception:")
+ print("---------------------8<---------------------")
+ traceback.print_exc()
+ print("---------------------8<---------------------")
+ print("(you may still try to recover from this manually by using the 'equip' command.)")
+ print(" it should also be noted that some readers may behave strangely when no card")
+ print(" is inserted.)")
+ print("")
+ app = PysimApp(None, None, opts.script)
# If the user supplies an ADM PIN at via commandline args authenticate
# immediately so that the user does not have to use the shell commands
pin_adm = sanitize_pin_adm(opts.pin_adm, opts.pin_adm_hex)
if pin_adm:
+ if not card:
+ print("Card error, cannot do ADM verification with supplied ADM pin now.")
try:
card.verify_adm(h2b(pin_adm))
except Exception as e:
diff --git a/pySim/filesystem.py b/pySim/filesystem.py
index fe781de..cc9176f 100644
--- a/pySim/filesystem.py
+++ b/pySim/filesystem.py
@@ -1329,6 +1329,11 @@ class RuntimeState(object):
raise TypeError("Only works with BER-TLV EF")
return self.card._scc.set_data(self.selected_file.fid, tag, data_hex, conserve=self.conserve_write)
+ def unregister_cmds(self, cmd_app=None):
+ """Unregister all file specific commands."""
+ if cmd_app and self.selected_file.shell_commands:
+ for c in self.selected_file.shell_commands:
+ cmd_app.unregister_command_set(c)