blob: 89fdf6ed7cf7e6bf4ba325612b56e8854ac05d2b [file] [log] [blame]
Harald Welteb2edd142021-01-08 23:29:35 +01001#!/usr/bin/env python3
2
3# Interactive shell for working with SIM / UICC / USIM / ISIM cards
4#
Harald Welte8fab4632023-11-03 01:03:28 +01005# (C) 2021-2023 by Harald Welte <laforge@osmocom.org>
Harald Welteb2edd142021-01-08 23:29:35 +01006#
7# This program is free software: you can redistribute it and/or modify
8# it under the terms of the GNU General Public License as published by
9# the Free Software Foundation, either version 2 of the License, or
10# (at your option) any later version.
11#
12# This program is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15# GNU General Public License for more details.
16#
17# You should have received a copy of the GNU General Public License
18# along with this program. If not, see <http://www.gnu.org/licenses/>.
19
Harald Weltee1268722023-06-24 07:57:08 +020020from typing import List, Optional
Harald Welteb2edd142021-01-08 23:29:35 +010021
22import json
Philipp Maier5d698e52021-09-16 13:18:01 +020023import traceback
Harald Welteb2edd142021-01-08 23:29:35 +010024
25import cmd2
Harald Welte93aac3a2023-04-27 15:18:13 +020026from packaging import version
Harald Welte961b8032023-04-27 17:30:22 +020027from cmd2 import style
28# cmd2 >= 2.3.0 has deprecated the bg/fg in favor of Bg/Fg :(
29if version.parse(cmd2.__version__) < version.parse("2.3.0"):
Oliver Smithe47ea5f2023-05-23 12:54:15 +020030 from cmd2 import fg, bg # pylint: disable=no-name-in-module
Harald Welte961b8032023-04-27 17:30:22 +020031 RED = fg.red
32 LIGHT_RED = fg.bright_red
33 LIGHT_GREEN = fg.bright_green
34else:
35 from cmd2 import Fg, Bg # pylint: disable=no-name-in-module
36 RED = Fg.RED
37 LIGHT_RED = Fg.LIGHT_RED
38 LIGHT_GREEN = Fg.LIGHT_GREEN
Harald Welteb2edd142021-01-08 23:29:35 +010039from cmd2 import CommandSet, with_default_category, with_argparser
40import argparse
41
42import os
43import sys
Harald Welte6ad9a242023-07-11 18:55:29 +020044import inspect
Philipp Maier2b11c322021-03-17 12:37:39 +010045from pathlib import Path
Philipp Maier76667642021-09-22 16:53:22 +020046from io import StringIO
Harald Welteb2edd142021-01-08 23:29:35 +010047
Harald Weltecab26c72022-08-06 16:12:30 +020048from pprint import pprint as pp
49
Harald Welteb2edd142021-01-08 23:29:35 +010050from pySim.exceptions import *
Harald Weltecab26c72022-08-06 16:12:30 +020051from pySim.transport import init_reader, ApduTracer, argparse_add_reader_args, ProactiveHandler
Philipp Maiere345e112023-06-09 15:19:56 +020052from pySim.utils import h2b, b2h, i2h, swap_nibbles, rpad, JsonEncoder, bertlv_parse_one, sw_match
Philipp Maiera42ee6f2023-08-16 10:44:57 +020053from pySim.utils import sanitize_pin_adm, tabulate_str_list, boxed_heading_str, Hexstr, dec_iccid
Harald Welte1c849f82023-11-01 23:48:28 +010054from pySim.utils import is_hexstr_or_decimal, is_hexstr, is_decimal
Philipp Maier76667642021-09-22 16:53:22 +020055from pySim.card_handler import CardHandler, CardHandlerAuto
Harald Welteb2edd142021-01-08 23:29:35 +010056
Philipp Maier54827372023-10-24 16:18:30 +020057from pySim.filesystem import CardMF, CardDF, CardADF
Harald Welte3c9b7842021-10-19 21:44:24 +020058from pySim.ts_102_222 import Ts102222Commands
Harald Welte2a33ad22021-10-20 10:14:18 +020059from pySim.gsm_r import DF_EIRENE
Harald Weltecab26c72022-08-06 16:12:30 +020060from pySim.cat import ProactiveCommand
Harald Welteb2edd142021-01-08 23:29:35 +010061
Harald Welte4442b3d2021-04-03 09:00:16 +020062from pySim.card_key_provider import CardKeyProviderCsv, card_key_provider_register, card_key_provider_get_field
Philipp Maier2b11c322021-03-17 12:37:39 +010063
Harald Welte8fab4632023-11-03 01:03:28 +010064from pySim.app import init_card
Harald Weltec91085e2022-02-10 18:05:45 +010065
Philipp Maier2b11c322021-03-17 12:37:39 +010066
Harald Weltee1268722023-06-24 07:57:08 +020067class Cmd2Compat(cmd2.Cmd):
68 """Backwards-compatibility wrapper around cmd2.Cmd to support older and newer
69 releases. See https://github.com/python-cmd2/cmd2/blob/master/CHANGELOG.md"""
70 def run_editor(self, file_path: Optional[str] = None) -> None:
71 if version.parse(cmd2.__version__) < version.parse("2.0.0"):
Harald Welte0ec01502023-06-24 10:00:12 +020072 return self._run_editor(file_path) # pylint: disable=no-member
Harald Weltee1268722023-06-24 07:57:08 +020073 else:
Harald Welte0ec01502023-06-24 10:00:12 +020074 return super().run_editor(file_path) # pylint: disable=no-member
75
76class Settable2Compat(cmd2.Settable):
77 """Backwards-compatibility wrapper around cmd2.Settable to support older and newer
78 releases. See https://github.com/python-cmd2/cmd2/blob/master/CHANGELOG.md"""
79 def __init__(self, name, val_type, description, settable_object, **kwargs):
80 if version.parse(cmd2.__version__) < version.parse("2.0.0"):
81 super().__init__(name, val_type, description, **kwargs) # pylint: disable=no-value-for-parameter
82 else:
83 super().__init__(name, val_type, description, settable_object, **kwargs) # pylint: disable=too-many-function-args
Harald Weltee1268722023-06-24 07:57:08 +020084
85class PysimApp(Cmd2Compat):
Harald Weltec91085e2022-02-10 18:05:45 +010086 CUSTOM_CATEGORY = 'pySim Commands'
Harald Welte0ba3fd92023-11-01 19:18:24 +010087 BANNER = """Welcome to pySim-shell!
88(C) 2021-2023 by Harald Welte, sysmocom - s.f.m.c. GmbH and contributors
89Online manual available at https://downloads.osmocom.org/docs/pysim/master/html/shell.html """
Philipp Maier76667642021-09-22 16:53:22 +020090
Harald Weltec91085e2022-02-10 18:05:45 +010091 def __init__(self, card, rs, sl, ch, script=None):
Harald Weltec85d4062023-04-27 17:10:17 +020092 if version.parse(cmd2.__version__) < version.parse("2.0.0"):
93 kwargs = {'use_ipython': True}
94 else:
95 kwargs = {'include_ipy': True}
96
Oliver Smithe47ea5f2023-05-23 12:54:15 +020097 # pylint: disable=unexpected-keyword-arg
Harald Weltec91085e2022-02-10 18:05:45 +010098 super().__init__(persistent_history_file='~/.pysim_shell_history', allow_cli_args=False,
Harald Weltec85d4062023-04-27 17:10:17 +020099 auto_load_commands=False, startup_script=script, **kwargs)
Harald Welte0ba3fd92023-11-01 19:18:24 +0100100 self.intro = style(self.BANNER, fg=RED)
Harald Weltec91085e2022-02-10 18:05:45 +0100101 self.default_category = 'pySim-shell built-in commands'
102 self.card = None
103 self.rs = None
Harald Weltea6c0f882022-07-17 14:23:17 +0200104 self.lchan = None
105 self.py_locals = {'card': self.card, 'rs': self.rs, 'lchan': self.lchan}
Harald Weltec91085e2022-02-10 18:05:45 +0100106 self.sl = sl
107 self.ch = ch
Harald Welte1748b932021-04-06 21:12:25 +0200108
Harald Weltec91085e2022-02-10 18:05:45 +0100109 self.numeric_path = False
Harald Weltec91085e2022-02-10 18:05:45 +0100110 self.conserve_write = True
Harald Weltec91085e2022-02-10 18:05:45 +0100111 self.json_pretty_print = True
Harald Weltec91085e2022-02-10 18:05:45 +0100112 self.apdu_trace = False
Philipp Maier5d698e52021-09-16 13:18:01 +0200113
Harald Welte0ec01502023-06-24 10:00:12 +0200114 self.add_settable(Settable2Compat('numeric_path', bool, 'Print File IDs instead of names', self,
115 onchange_cb=self._onchange_numeric_path))
116 self.add_settable(Settable2Compat('conserve_write', bool, 'Read and compare before write', self,
117 onchange_cb=self._onchange_conserve_write))
118 self.add_settable(Settable2Compat('json_pretty_print', bool, 'Pretty-Print JSON output', self))
119 self.add_settable(Settable2Compat('apdu_trace', bool, 'Trace and display APDUs exchanged with card', self,
120 onchange_cb=self._onchange_apdu_trace))
Harald Weltec91085e2022-02-10 18:05:45 +0100121 self.equip(card, rs)
Philipp Maier5d698e52021-09-16 13:18:01 +0200122
Harald Weltec91085e2022-02-10 18:05:45 +0100123 def equip(self, card, rs):
124 """
125 Equip pySim-shell with the supplied card and runtime state, add (or remove) all required settables and
126 and commands to enable card operations.
127 """
Philipp Maier76667642021-09-22 16:53:22 +0200128
Harald Weltec91085e2022-02-10 18:05:45 +0100129 rc = False
Philipp Maier5d698e52021-09-16 13:18:01 +0200130
Harald Weltec91085e2022-02-10 18:05:45 +0100131 # Unequip everything from pySim-shell that would not work in unequipped state
132 if self.rs:
Harald Weltea6c0f882022-07-17 14:23:17 +0200133 lchan = self.rs.lchan[0]
134 lchan.unregister_cmds(self)
iw0f818acd2023-06-19 10:27:04 +0200135 if self.rs.profile:
136 for cmd_set in self.rs.profile.shell_cmdsets:
137 self.unregister_command_set(cmd_set)
138
Harald Welte892526f2023-06-06 17:09:50 +0200139 for cmds in [Iso7816Commands, Ts102222Commands, PySimCommands]:
Harald Weltec91085e2022-02-10 18:05:45 +0100140 cmd_set = self.find_commandsets(cmds)
141 if cmd_set:
142 self.unregister_command_set(cmd_set[0])
Philipp Maier5d698e52021-09-16 13:18:01 +0200143
Harald Weltec91085e2022-02-10 18:05:45 +0100144 self.card = card
145 self.rs = rs
Philipp Maier5d698e52021-09-16 13:18:01 +0200146
Harald Weltec91085e2022-02-10 18:05:45 +0100147 # When a card object and a runtime state is present, (re)equip pySim-shell with everything that is
148 # needed to operate on cards.
149 if self.card and self.rs:
Harald Weltea6c0f882022-07-17 14:23:17 +0200150 self.lchan = self.rs.lchan[0]
Harald Weltec91085e2022-02-10 18:05:45 +0100151 self._onchange_conserve_write(
152 'conserve_write', False, self.conserve_write)
153 self._onchange_apdu_trace('apdu_trace', False, self.apdu_trace)
Harald Welte659781c2023-06-06 17:00:51 +0200154 if self.rs.profile:
155 for cmd_set in self.rs.profile.shell_cmdsets:
156 self.register_command_set(cmd_set)
Harald Weltec91085e2022-02-10 18:05:45 +0100157 self.register_command_set(Iso7816Commands())
Harald Welte3c9b7842021-10-19 21:44:24 +0200158 self.register_command_set(Ts102222Commands())
Harald Weltec91085e2022-02-10 18:05:45 +0100159 self.register_command_set(PySimCommands())
Philipp Maiera42ee6f2023-08-16 10:44:57 +0200160
Philipp Maier7c0cd0a2023-10-16 15:02:07 +0200161 try:
162 self.lchan.select('MF/EF.ICCID', self)
163 self.iccid = dec_iccid(self.lchan.read_binary()[0])
164 except:
165 self.iccid = None
Philipp Maiera42ee6f2023-08-16 10:44:57 +0200166
Harald Weltea6c0f882022-07-17 14:23:17 +0200167 self.lchan.select('MF', self)
Harald Weltec91085e2022-02-10 18:05:45 +0100168 rc = True
169 else:
170 self.poutput("pySim-shell not equipped!")
Philipp Maier5d698e52021-09-16 13:18:01 +0200171
Harald Weltec91085e2022-02-10 18:05:45 +0100172 self.update_prompt()
173 return rc
Harald Welteb2edd142021-01-08 23:29:35 +0100174
Harald Weltec91085e2022-02-10 18:05:45 +0100175 def poutput_json(self, data, force_no_pretty=False):
176 """like cmd2.poutput() but for a JSON serializable dict."""
177 if force_no_pretty or self.json_pretty_print == False:
178 output = json.dumps(data, cls=JsonEncoder)
179 else:
180 output = json.dumps(data, cls=JsonEncoder, indent=4)
181 self.poutput(output)
Harald Welteb2edd142021-01-08 23:29:35 +0100182
Harald Weltec91085e2022-02-10 18:05:45 +0100183 def _onchange_numeric_path(self, param_name, old, new):
184 self.update_prompt()
Philipp Maier38c74f62021-03-17 17:19:52 +0100185
Harald Weltec91085e2022-02-10 18:05:45 +0100186 def _onchange_conserve_write(self, param_name, old, new):
187 if self.rs:
188 self.rs.conserve_write = new
Harald Welte7829d8a2021-04-10 11:28:53 +0200189
Harald Weltec91085e2022-02-10 18:05:45 +0100190 def _onchange_apdu_trace(self, param_name, old, new):
191 if self.card:
192 if new == True:
193 self.card._scc._tp.apdu_tracer = self.Cmd2ApduTracer(self)
194 else:
195 self.card._scc._tp.apdu_tracer = None
Harald Welte7829d8a2021-04-10 11:28:53 +0200196
Harald Weltec91085e2022-02-10 18:05:45 +0100197 class Cmd2ApduTracer(ApduTracer):
198 def __init__(self, cmd2_app):
Philipp Maier91c971b2023-10-09 10:48:46 +0200199 self.cmd2 = cmd2_app
Harald Welte7829d8a2021-04-10 11:28:53 +0200200
Harald Weltec91085e2022-02-10 18:05:45 +0100201 def trace_response(self, cmd, sw, resp):
202 self.cmd2.poutput("-> %s %s" % (cmd[:10], cmd[10:]))
203 self.cmd2.poutput("<- %s: %s" % (sw, resp))
Harald Welteb2edd142021-01-08 23:29:35 +0100204
Harald Weltec91085e2022-02-10 18:05:45 +0100205 def update_prompt(self):
Harald Weltea6c0f882022-07-17 14:23:17 +0200206 if self.lchan:
Harald Welteb2e4b4a2022-07-19 23:48:45 +0200207 path_str = self.lchan.selected_file.fully_qualified_path_str(not self.numeric_path)
Harald Welte41a73792023-12-28 20:51:52 +0100208 scp = self.lchan.scc.scp
209 if scp:
210 self.prompt = 'pySIM-shell (%s:%02u:%s)> ' % (str(scp), self.lchan.lchan_nr, path_str)
211 else:
212 self.prompt = 'pySIM-shell (%02u:%s)> ' % (self.lchan.lchan_nr, path_str)
Harald Weltec91085e2022-02-10 18:05:45 +0100213 else:
Philipp Maier7226c092022-06-01 17:58:38 +0200214 if self.card:
215 self.prompt = 'pySIM-shell (no card profile)> '
216 else:
217 self.prompt = 'pySIM-shell (no card)> '
Harald Welteb2edd142021-01-08 23:29:35 +0100218
Harald Weltec91085e2022-02-10 18:05:45 +0100219 @cmd2.with_category(CUSTOM_CATEGORY)
220 def do_intro(self, _):
221 """Display the intro banner"""
222 self.poutput(self.intro)
Philipp Maier9764de22021-11-03 10:44:39 +0100223
Harald Weltec91085e2022-02-10 18:05:45 +0100224 def do_eof(self, _: argparse.Namespace) -> bool:
225 self.poutput("")
226 return self.do_quit('')
Philipp Maier5d698e52021-09-16 13:18:01 +0200227
Harald Weltec91085e2022-02-10 18:05:45 +0100228 @cmd2.with_category(CUSTOM_CATEGORY)
229 def do_equip(self, opts):
230 """Equip pySim-shell with card"""
Philipp Maiere6cba762023-08-11 11:23:17 +0200231 if self.rs and self.rs.profile:
Harald Welte659781c2023-06-06 17:00:51 +0200232 for cmd_set in self.rs.profile.shell_cmdsets:
233 self.unregister_command_set(cmd_set)
Philipp Maier91c971b2023-10-09 10:48:46 +0200234 rs, card = init_card(self.sl)
Harald Weltec91085e2022-02-10 18:05:45 +0100235 self.equip(card, rs)
Philipp Maier76667642021-09-22 16:53:22 +0200236
Philipp Maier7226c092022-06-01 17:58:38 +0200237 apdu_cmd_parser = argparse.ArgumentParser()
Harald Welte4e59d892023-11-01 23:40:07 +0100238 apdu_cmd_parser.add_argument('APDU', type=is_hexstr, help='APDU as hex string')
Philipp Maiere7d1b672022-06-01 18:05:34 +0200239 apdu_cmd_parser.add_argument('--expect-sw', help='expect a specified status word', type=str, default=None)
Harald Welte321973a2024-02-01 18:43:48 +0100240 apdu_cmd_parser.add_argument('--raw', help='Bypass the logical channel (and secure channel)', action='store_true')
Philipp Maier7226c092022-06-01 17:58:38 +0200241
242 @cmd2.with_argparser(apdu_cmd_parser)
243 def do_apdu(self, opts):
244 """Send a raw APDU to the card, and print SW + Response.
Philipp Maier1c207a22023-11-29 13:04:09 +0100245 CAUTION: this command bypasses the logical channel handling of pySim-shell and card state changes are not
246 tracked. Dpending on the raw APDU sent, pySim-shell may not continue to work as expected if you e.g. select
247 a different file."""
248
249 # When sending raw APDUs we access the scc object through _scc member of the card object. It should also be
250 # noted that the apdu command plays an exceptional role since it is the only card accessing command that
251 # can be executed without the presence of a runtime state (self.rs) object. However, this also means that
252 # self.lchan is also not present (see method equip).
Harald Welte321973a2024-02-01 18:43:48 +0100253 if opts.raw:
254 data, sw = self.card._scc.send_apdu(opts.APDU)
255 else:
256 data, sw = self.lchan.scc.send_apdu(opts.APDU)
Philipp Maier7226c092022-06-01 17:58:38 +0200257 if data:
258 self.poutput("SW: %s, RESP: %s" % (sw, data))
259 else:
260 self.poutput("SW: %s" % sw)
Philipp Maiere7d1b672022-06-01 18:05:34 +0200261 if opts.expect_sw:
262 if not sw_match(sw, opts.expect_sw):
263 raise SwMatchError(sw, opts.expect_sw)
Philipp Maier7226c092022-06-01 17:58:38 +0200264
Harald Welte66b33702024-01-16 18:59:43 +0100265 @cmd2.with_category(CUSTOM_CATEGORY)
266 def do_reset(self, opts):
267 """Reset the Card."""
268 atr = self.card.reset()
Harald Welte41a73792023-12-28 20:51:52 +0100269 if self.lchan and self.lchan.scc.scp:
270 self.lchan.scc.scp = None
Harald Welte66b33702024-01-16 18:59:43 +0100271 self.poutput('Card ATR: %s' % i2h(atr))
272 self.update_prompt()
273
Harald Weltec91085e2022-02-10 18:05:45 +0100274 class InterceptStderr(list):
275 def __init__(self):
276 self._stderr_backup = sys.stderr
Philipp Maier76667642021-09-22 16:53:22 +0200277
Harald Weltec91085e2022-02-10 18:05:45 +0100278 def __enter__(self):
279 self._stringio_stderr = StringIO()
280 sys.stderr = self._stringio_stderr
281 return self
Philipp Maier76667642021-09-22 16:53:22 +0200282
Harald Weltec91085e2022-02-10 18:05:45 +0100283 def __exit__(self, *args):
284 self.stderr = self._stringio_stderr.getvalue().strip()
285 del self._stringio_stderr
286 sys.stderr = self._stderr_backup
Philipp Maier76667642021-09-22 16:53:22 +0200287
Harald Weltec91085e2022-02-10 18:05:45 +0100288 def _show_failure_sign(self):
Harald Welte961b8032023-04-27 17:30:22 +0200289 self.poutput(style(" +-------------+", fg=LIGHT_RED))
290 self.poutput(style(" + ## ## +", fg=LIGHT_RED))
291 self.poutput(style(" + ## ## +", fg=LIGHT_RED))
292 self.poutput(style(" + ### +", fg=LIGHT_RED))
293 self.poutput(style(" + ## ## +", fg=LIGHT_RED))
294 self.poutput(style(" + ## ## +", fg=LIGHT_RED))
295 self.poutput(style(" +-------------+", fg=LIGHT_RED))
Harald Weltec91085e2022-02-10 18:05:45 +0100296 self.poutput("")
Philipp Maier76667642021-09-22 16:53:22 +0200297
Harald Weltec91085e2022-02-10 18:05:45 +0100298 def _show_success_sign(self):
Harald Welte961b8032023-04-27 17:30:22 +0200299 self.poutput(style(" +-------------+", fg=LIGHT_GREEN))
300 self.poutput(style(" + ## +", fg=LIGHT_GREEN))
301 self.poutput(style(" + ## +", fg=LIGHT_GREEN))
302 self.poutput(style(" + # ## +", fg=LIGHT_GREEN))
303 self.poutput(style(" + ## # +", fg=LIGHT_GREEN))
304 self.poutput(style(" + ## +", fg=LIGHT_GREEN))
305 self.poutput(style(" +-------------+", fg=LIGHT_GREEN))
Harald Weltec91085e2022-02-10 18:05:45 +0100306 self.poutput("")
Philipp Maier76667642021-09-22 16:53:22 +0200307
Harald Weltec91085e2022-02-10 18:05:45 +0100308 def _process_card(self, first, script_path):
Philipp Maier76667642021-09-22 16:53:22 +0200309
Harald Weltec91085e2022-02-10 18:05:45 +0100310 # Early phase of card initialzation (this part may fail with an exception)
311 try:
312 rs, card = init_card(self.sl)
313 rc = self.equip(card, rs)
314 except:
315 self.poutput("")
Philipp Maier6bfa8a82023-10-09 13:32:49 +0200316 self.poutput("Card initialization (%s) failed with an exception:" % str(self.sl))
Harald Weltec91085e2022-02-10 18:05:45 +0100317 self.poutput("---------------------8<---------------------")
318 traceback.print_exc()
319 self.poutput("---------------------8<---------------------")
320 self.poutput("")
321 return -1
Philipp Maier76667642021-09-22 16:53:22 +0200322
Harald Weltec91085e2022-02-10 18:05:45 +0100323 # Actual card processing step. This part should never fail with an exception since the cmd2
324 # do_run_script method will catch any exception that might occur during script execution.
325 if rc:
326 self.poutput("")
327 self.poutput("Transcript stdout:")
328 self.poutput("---------------------8<---------------------")
329 with self.InterceptStderr() as logged:
330 self.do_run_script(script_path)
331 self.poutput("---------------------8<---------------------")
Philipp Maier76667642021-09-22 16:53:22 +0200332
Harald Weltec91085e2022-02-10 18:05:45 +0100333 self.poutput("")
334 self.poutput("Transcript stderr:")
335 if logged.stderr:
336 self.poutput("---------------------8<---------------------")
337 self.poutput(logged.stderr)
338 self.poutput("---------------------8<---------------------")
339 else:
340 self.poutput("(none)")
Philipp Maier76667642021-09-22 16:53:22 +0200341
Harald Weltec91085e2022-02-10 18:05:45 +0100342 # Check for exceptions
343 self.poutput("")
344 if "EXCEPTION of type" not in logged.stderr:
345 return 0
Philipp Maier76667642021-09-22 16:53:22 +0200346
Harald Weltec91085e2022-02-10 18:05:45 +0100347 return -1
Philipp Maier76667642021-09-22 16:53:22 +0200348
Harald Weltec91085e2022-02-10 18:05:45 +0100349 bulk_script_parser = argparse.ArgumentParser()
350 bulk_script_parser.add_argument(
351 'script_path', help="path to the script file")
352 bulk_script_parser.add_argument('--halt_on_error', help='stop card handling if an exeption occurs',
353 action='store_true')
354 bulk_script_parser.add_argument('--tries', type=int, default=2,
355 help='how many tries before trying the next card')
356 bulk_script_parser.add_argument('--on_stop_action', type=str, default=None,
357 help='commandline to execute when card handling has stopped')
358 bulk_script_parser.add_argument('--pre_card_action', type=str, default=None,
359 help='commandline to execute before actually talking to the card')
Philipp Maier76667642021-09-22 16:53:22 +0200360
Harald Weltec91085e2022-02-10 18:05:45 +0100361 @cmd2.with_argparser(bulk_script_parser)
362 @cmd2.with_category(CUSTOM_CATEGORY)
363 def do_bulk_script(self, opts):
364 """Run script on multiple cards (bulk provisioning)"""
Philipp Maier76667642021-09-22 16:53:22 +0200365
Harald Weltec91085e2022-02-10 18:05:45 +0100366 # Make sure that the script file exists and that it is readable.
367 if not os.access(opts.script_path, os.R_OK):
368 self.poutput("Invalid script file!")
369 return
Philipp Maier76667642021-09-22 16:53:22 +0200370
Harald Weltec91085e2022-02-10 18:05:45 +0100371 success_count = 0
372 fail_count = 0
Philipp Maier76667642021-09-22 16:53:22 +0200373
Harald Weltec91085e2022-02-10 18:05:45 +0100374 first = True
375 while 1:
376 # TODO: Count consecutive failures, if more than N consecutive failures occur, then stop.
377 # The ratinale is: There may be a problem with the device, we do want to prevent that
378 # all remaining cards are fired to the error bin. This is only relevant for situations
379 # with large stacks, probably we do not need this feature right now.
Philipp Maier76667642021-09-22 16:53:22 +0200380
Harald Weltec91085e2022-02-10 18:05:45 +0100381 try:
382 # In case of failure, try multiple times.
383 for i in range(opts.tries):
384 # fetch card into reader bay
Philipp Maier91c971b2023-10-09 10:48:46 +0200385 self.ch.get(first)
Philipp Maier76667642021-09-22 16:53:22 +0200386
Harald Weltec91085e2022-02-10 18:05:45 +0100387 # if necessary execute an action before we start processing the card
388 if(opts.pre_card_action):
389 os.system(opts.pre_card_action)
Philipp Maier76667642021-09-22 16:53:22 +0200390
Harald Weltec91085e2022-02-10 18:05:45 +0100391 # process the card
392 rc = self._process_card(first, opts.script_path)
393 if rc == 0:
394 success_count = success_count + 1
395 self._show_success_sign()
396 self.poutput("Statistics: success :%i, failure: %i" % (
397 success_count, fail_count))
398 break
399 else:
400 fail_count = fail_count + 1
401 self._show_failure_sign()
402 self.poutput("Statistics: success :%i, failure: %i" % (
403 success_count, fail_count))
Philipp Maier76667642021-09-22 16:53:22 +0200404
Harald Weltec91085e2022-02-10 18:05:45 +0100405 # Depending on success or failure, the card goes either in the "error" bin or in the
406 # "done" bin.
407 if rc < 0:
Philipp Maier91c971b2023-10-09 10:48:46 +0200408 self.ch.error()
Harald Weltec91085e2022-02-10 18:05:45 +0100409 else:
Philipp Maier91c971b2023-10-09 10:48:46 +0200410 self.ch.done()
Philipp Maier76667642021-09-22 16:53:22 +0200411
Harald Weltec91085e2022-02-10 18:05:45 +0100412 # In most cases it is possible to proceed with the next card, but the
413 # user may decide to halt immediately when an error occurs
414 if opts.halt_on_error and rc < 0:
415 return
Philipp Maier76667642021-09-22 16:53:22 +0200416
Harald Weltec91085e2022-02-10 18:05:45 +0100417 except (KeyboardInterrupt):
418 self.poutput("")
419 self.poutput("Terminated by user!")
420 return
421 except (SystemExit):
422 # When all cards are processed the card handler device will throw a SystemExit
423 # exception. Also Errors that are not recoverable (cards stuck etc.) will end up here.
424 # The user has the option to execute some action to make aware that the card handler
425 # needs service.
426 if(opts.on_stop_action):
427 os.system(opts.on_stop_action)
428 return
429 except:
430 self.poutput("")
Philipp Maier6bfa8a82023-10-09 13:32:49 +0200431 self.poutput("Card handling (%s) failed with an exception:" % str(self.sl))
Harald Weltec91085e2022-02-10 18:05:45 +0100432 self.poutput("---------------------8<---------------------")
433 traceback.print_exc()
434 self.poutput("---------------------8<---------------------")
435 self.poutput("")
436 fail_count = fail_count + 1
437 self._show_failure_sign()
438 self.poutput("Statistics: success :%i, failure: %i" %
439 (success_count, fail_count))
Philipp Maierb52feed2021-09-22 16:43:13 +0200440
Harald Weltec91085e2022-02-10 18:05:45 +0100441 first = False
442
443 echo_parser = argparse.ArgumentParser()
Harald Welte977c5922023-11-01 23:42:55 +0100444 echo_parser.add_argument('string', help="string to echo on the shell", nargs='+')
Harald Weltec91085e2022-02-10 18:05:45 +0100445
446 @cmd2.with_argparser(echo_parser)
447 @cmd2.with_category(CUSTOM_CATEGORY)
448 def do_echo(self, opts):
449 """Echo (print) a string on the console"""
Harald Welte977c5922023-11-01 23:42:55 +0100450 self.poutput(' '.join(opts.string))
Harald Weltec91085e2022-02-10 18:05:45 +0100451
Harald Weltefc315482022-07-23 12:49:14 +0200452 @cmd2.with_category(CUSTOM_CATEGORY)
453 def do_version(self, opts):
454 """Print the pySim software version."""
455 import pkg_resources
456 self.poutput(pkg_resources.get_distribution('pySim'))
Harald Welteb2edd142021-01-08 23:29:35 +0100457
Harald Welte31d2cf02021-04-03 10:47:29 +0200458@with_default_category('pySim Commands')
459class PySimCommands(CommandSet):
Harald Weltec91085e2022-02-10 18:05:45 +0100460 def __init__(self):
461 super().__init__()
Harald Welteb2edd142021-01-08 23:29:35 +0100462
Harald Weltec91085e2022-02-10 18:05:45 +0100463 dir_parser = argparse.ArgumentParser()
464 dir_parser.add_argument(
465 '--fids', help='Show file identifiers', action='store_true')
466 dir_parser.add_argument(
467 '--names', help='Show file names', action='store_true')
468 dir_parser.add_argument(
469 '--apps', help='Show applications', action='store_true')
470 dir_parser.add_argument(
471 '--all', help='Show all selectable identifiers and names', action='store_true')
Philipp Maier5d3e2592021-02-22 17:22:16 +0100472
Harald Weltec91085e2022-02-10 18:05:45 +0100473 @cmd2.with_argparser(dir_parser)
474 def do_dir(self, opts):
475 """Show a listing of files available in currently selected DF or MF"""
476 if opts.all:
477 flags = []
478 elif opts.fids or opts.names or opts.apps:
479 flags = ['PARENT', 'SELF']
480 if opts.fids:
481 flags += ['FIDS', 'AIDS']
482 if opts.names:
483 flags += ['FNAMES', 'ANAMES']
484 if opts.apps:
485 flags += ['ANAMES', 'AIDS']
486 else:
487 flags = ['PARENT', 'SELF', 'FNAMES', 'ANAMES']
488 selectables = list(
Harald Weltea6c0f882022-07-17 14:23:17 +0200489 self._cmd.lchan.selected_file.get_selectable_names(flags=flags))
Harald Weltec91085e2022-02-10 18:05:45 +0100490 directory_str = tabulate_str_list(
491 selectables, width=79, hspace=2, lspace=1, align_left=True)
Harald Welteb2e4b4a2022-07-19 23:48:45 +0200492 path = self._cmd.lchan.selected_file.fully_qualified_path_str(True)
493 self._cmd.poutput(path)
494 path = self._cmd.lchan.selected_file.fully_qualified_path_str(False)
495 self._cmd.poutput(path)
Harald Weltec91085e2022-02-10 18:05:45 +0100496 self._cmd.poutput(directory_str)
497 self._cmd.poutput("%d files" % len(selectables))
Harald Welteb2edd142021-01-08 23:29:35 +0100498
Philipp Maier7b138b02022-05-31 13:42:56 +0200499 def walk(self, indent=0, action_ef=None, action_df=None, context=None, **kwargs):
Harald Weltec91085e2022-02-10 18:05:45 +0100500 """Recursively walk through the file system, starting at the currently selected DF"""
Philipp Maier7b138b02022-05-31 13:42:56 +0200501
Harald Weltea6c0f882022-07-17 14:23:17 +0200502 if isinstance(self._cmd.lchan.selected_file, CardDF):
Philipp Maier7b138b02022-05-31 13:42:56 +0200503 if action_df:
Philipp Maier91c971b2023-10-09 10:48:46 +0200504 action_df(context, **kwargs)
Philipp Maier7b138b02022-05-31 13:42:56 +0200505
Harald Weltea6c0f882022-07-17 14:23:17 +0200506 files = self._cmd.lchan.selected_file.get_selectables(
Harald Weltec91085e2022-02-10 18:05:45 +0100507 flags=['FNAMES', 'ANAMES'])
508 for f in files:
Philipp Maier7b138b02022-05-31 13:42:56 +0200509 # special case: When no action is performed, just output a directory
510 if not action_ef and not action_df:
Harald Weltec91085e2022-02-10 18:05:45 +0100511 output_str = " " * indent + str(f) + (" " * 250)
512 output_str = output_str[0:25]
513 if isinstance(files[f], CardADF):
514 output_str += " " + str(files[f].aid)
515 else:
516 output_str += " " + str(files[f].fid)
517 output_str += " " + str(files[f].desc)
518 self._cmd.poutput(output_str)
Philipp Maierf408a402021-04-09 21:16:12 +0200519
Harald Weltec91085e2022-02-10 18:05:45 +0100520 if isinstance(files[f], CardDF):
521 skip_df = False
522 try:
Harald Weltea6c0f882022-07-17 14:23:17 +0200523 fcp_dec = self._cmd.lchan.select(f, self._cmd)
Harald Weltec91085e2022-02-10 18:05:45 +0100524 except Exception as e:
525 skip_df = True
Harald Weltea6c0f882022-07-17 14:23:17 +0200526 df = self._cmd.lchan.selected_file
Harald Welteb2e4b4a2022-07-19 23:48:45 +0200527 df_path = df.fully_qualified_path_str(True)
528 df_skip_reason_str = df_path + \
Harald Weltec91085e2022-02-10 18:05:45 +0100529 "/" + str(f) + ", " + str(e)
530 if context:
531 context['DF_SKIP'] += 1
532 context['DF_SKIP_REASON'].append(df_skip_reason_str)
Philipp Maierf408a402021-04-09 21:16:12 +0200533
Harald Weltec91085e2022-02-10 18:05:45 +0100534 # If the DF was skipped, we never have entered the directory
535 # below, so we must not move up.
536 if skip_df == False:
Philipp Maier7b138b02022-05-31 13:42:56 +0200537 self.walk(indent + 1, action_ef, action_df, context, **kwargs)
Philipp Maier54827372023-10-24 16:18:30 +0200538
539 parent = self._cmd.lchan.selected_file.parent
540 df = self._cmd.lchan.selected_file
541 adf = self._cmd.lchan.selected_adf
542 if isinstance(parent, CardMF) and (adf and adf.has_fs == False):
543 # Not every application that may be present on a GlobalPlatform card will support the SELECT
544 # command as we know it from ETSI TS 102 221, section 11.1.1. In fact the only subset of
545 # SELECT we may rely on is the OPEN SELECT command as specified in GlobalPlatform Card
546 # Specification, section 11.9. Unfortunately the OPEN SELECT command only supports the
547 # "select by name" method, which means we can only select an application and not a file.
548 # The consequence of this is that we may get trapped in an application that does not have
549 # ISIM/USIM like file system support and the only way to leave that application is to select
550 # an ISIM/USIM application in order to get the file system access back.
551 #
552 # To automate this escape-route while traversing the file system we will check whether
553 # the parent file is the MF. When this is the case and the selected ADF has no file system
554 # support, we will select an arbitrary ADF that has file system support first and from there
555 # we will then select the MF.
556 for selectable in parent.get_selectables().items():
557 if isinstance(selectable[1], CardADF) and selectable[1].has_fs == True:
558 self._cmd.lchan.select(selectable[1].name, self._cmd)
559 break
560 self._cmd.lchan.select(df.get_mf().name, self._cmd)
561 else:
562 # Normal DF/ADF selection
563 fcp_dec = self._cmd.lchan.select("..", self._cmd)
Philipp Maierf408a402021-04-09 21:16:12 +0200564
Philipp Maier7b138b02022-05-31 13:42:56 +0200565 elif action_ef:
Harald Weltea6c0f882022-07-17 14:23:17 +0200566 df_before_action = self._cmd.lchan.selected_file
Philipp Maier7b138b02022-05-31 13:42:56 +0200567 action_ef(f, context, **kwargs)
Harald Weltec91085e2022-02-10 18:05:45 +0100568 # When walking through the file system tree the action must not
569 # always restore the currently selected file to the file that
570 # was selected before executing the action() callback.
Harald Weltea6c0f882022-07-17 14:23:17 +0200571 if df_before_action != self._cmd.lchan.selected_file:
Harald Weltec91085e2022-02-10 18:05:45 +0100572 raise RuntimeError("inconsistent walk, %s is currently selected but expecting %s to be selected"
Harald Weltea6c0f882022-07-17 14:23:17 +0200573 % (str(self._cmd.lchan.selected_file), str(df_before_action)))
Philipp Maierff9dae22021-02-25 17:03:21 +0100574
Harald Weltec91085e2022-02-10 18:05:45 +0100575 def do_tree(self, opts):
576 """Display a filesystem-tree with all selectable files"""
577 self.walk()
Philipp Maierff9dae22021-02-25 17:03:21 +0100578
Philipp Maier7b138b02022-05-31 13:42:56 +0200579 def export_ef(self, filename, context, as_json):
580 """ Select and export a single elementary file (EF) """
Harald Weltec91085e2022-02-10 18:05:45 +0100581 context['COUNT'] += 1
Harald Weltea6c0f882022-07-17 14:23:17 +0200582 df = self._cmd.lchan.selected_file
Philipp Maierac34dcc2021-04-01 17:19:05 +0200583
Philipp Maierea81f752022-05-19 10:13:30 +0200584 # The currently selected file (not the file we are going to export)
585 # must always be an ADF or DF. From this starting point we select
586 # the EF we want to export. To maintain consistency we will then
587 # select the current DF again (see comment below).
Harald Weltec91085e2022-02-10 18:05:45 +0100588 if not isinstance(df, CardDF):
589 raise RuntimeError(
590 "currently selected file %s is not a DF or ADF" % str(df))
Philipp Maierac34dcc2021-04-01 17:19:05 +0200591
Harald Weltec91085e2022-02-10 18:05:45 +0100592 df_path_list = df.fully_qualified_path(True)
Harald Welteb2e4b4a2022-07-19 23:48:45 +0200593 df_path = df.fully_qualified_path_str(True)
594 df_path_fid = df.fully_qualified_path_str(False)
Philipp Maier24f7bd32021-02-25 17:06:18 +0100595
Harald Welteb2e4b4a2022-07-19 23:48:45 +0200596 file_str = df_path + "/" + str(filename)
Harald Weltec91085e2022-02-10 18:05:45 +0100597 self._cmd.poutput(boxed_heading_str(file_str))
Philipp Maier24f7bd32021-02-25 17:06:18 +0100598
Harald Welteb2e4b4a2022-07-19 23:48:45 +0200599 self._cmd.poutput("# directory: %s (%s)" % (df_path, df_path_fid))
Harald Weltec91085e2022-02-10 18:05:45 +0100600 try:
Harald Weltea6c0f882022-07-17 14:23:17 +0200601 fcp_dec = self._cmd.lchan.select(filename, self._cmd)
Harald Weltec91085e2022-02-10 18:05:45 +0100602 self._cmd.poutput("# file: %s (%s)" % (
Harald Weltea6c0f882022-07-17 14:23:17 +0200603 self._cmd.lchan.selected_file.name, self._cmd.lchan.selected_file.fid))
Philipp Maier24f7bd32021-02-25 17:06:18 +0100604
Harald Weltea6c0f882022-07-17 14:23:17 +0200605 structure = self._cmd.lchan.selected_file_structure()
Harald Weltec91085e2022-02-10 18:05:45 +0100606 self._cmd.poutput("# structure: %s" % str(structure))
Harald Weltea6c0f882022-07-17 14:23:17 +0200607 self._cmd.poutput("# RAW FCP Template: %s" % str(self._cmd.lchan.selected_file_fcp_hex))
608 self._cmd.poutput("# Decoded FCP Template: %s" % str(self._cmd.lchan.selected_file_fcp))
Philipp Maier24f7bd32021-02-25 17:06:18 +0100609
Harald Weltec91085e2022-02-10 18:05:45 +0100610 for f in df_path_list:
611 self._cmd.poutput("select " + str(f))
Harald Weltea6c0f882022-07-17 14:23:17 +0200612 self._cmd.poutput("select " + self._cmd.lchan.selected_file.name)
Philipp Maier24f7bd32021-02-25 17:06:18 +0100613
Harald Weltec91085e2022-02-10 18:05:45 +0100614 if structure == 'transparent':
Harald Welte08b11ab2022-02-10 18:56:41 +0100615 if as_json:
Harald Weltea6c0f882022-07-17 14:23:17 +0200616 result = self._cmd.lchan.read_binary_dec()
Harald Welte08b11ab2022-02-10 18:56:41 +0100617 self._cmd.poutput("update_binary_decoded '%s'" % json.dumps(result[0], cls=JsonEncoder))
618 else:
Harald Weltea6c0f882022-07-17 14:23:17 +0200619 result = self._cmd.lchan.read_binary()
Harald Welte08b11ab2022-02-10 18:56:41 +0100620 self._cmd.poutput("update_binary " + str(result[0]))
Harald Weltec91085e2022-02-10 18:05:45 +0100621 elif structure == 'cyclic' or structure == 'linear_fixed':
622 # Use number of records specified in select response
Harald Weltea6c0f882022-07-17 14:23:17 +0200623 num_of_rec = self._cmd.lchan.selected_file_num_of_rec()
Harald Welte747a9782022-02-13 17:52:28 +0100624 if num_of_rec:
Harald Weltec91085e2022-02-10 18:05:45 +0100625 for r in range(1, num_of_rec + 1):
Harald Welte08b11ab2022-02-10 18:56:41 +0100626 if as_json:
Harald Weltea6c0f882022-07-17 14:23:17 +0200627 result = self._cmd.lchan.read_record_dec(r)
Harald Welte08b11ab2022-02-10 18:56:41 +0100628 self._cmd.poutput("update_record_decoded %d '%s'" % (r, json.dumps(result[0], cls=JsonEncoder)))
629 else:
Harald Weltea6c0f882022-07-17 14:23:17 +0200630 result = self._cmd.lchan.read_record(r)
Harald Welte08b11ab2022-02-10 18:56:41 +0100631 self._cmd.poutput("update_record %d %s" % (r, str(result[0])))
632
Harald Weltec91085e2022-02-10 18:05:45 +0100633 # When the select response does not return the number of records, read until we hit the
634 # first record that cannot be read.
635 else:
636 r = 1
637 while True:
638 try:
Harald Welte08b11ab2022-02-10 18:56:41 +0100639 if as_json:
Harald Weltea6c0f882022-07-17 14:23:17 +0200640 result = self._cmd.lchan.read_record_dec(r)
Harald Welte08b11ab2022-02-10 18:56:41 +0100641 self._cmd.poutput("update_record_decoded %d '%s'" % (r, json.dumps(result[0], cls=JsonEncoder)))
642 else:
Harald Weltea6c0f882022-07-17 14:23:17 +0200643 result = self._cmd.lchan.read_record(r)
Harald Welte08b11ab2022-02-10 18:56:41 +0100644 self._cmd.poutput("update_record %d %s" % (r, str(result[0])))
Harald Weltec91085e2022-02-10 18:05:45 +0100645 except SwMatchError as e:
646 # We are past the last valid record - stop
647 if e.sw_actual == "9402":
648 break
649 # Some other problem occurred
650 else:
651 raise e
Harald Weltec91085e2022-02-10 18:05:45 +0100652 r = r + 1
653 elif structure == 'ber_tlv':
Harald Weltea6c0f882022-07-17 14:23:17 +0200654 tags = self._cmd.lchan.retrieve_tags()
Harald Weltec91085e2022-02-10 18:05:45 +0100655 for t in tags:
Harald Weltea6c0f882022-07-17 14:23:17 +0200656 result = self._cmd.lchan.retrieve_data(t)
Harald Weltec91085e2022-02-10 18:05:45 +0100657 (tag, l, val, remainer) = bertlv_parse_one(h2b(result[0]))
658 self._cmd.poutput("set_data 0x%02x %s" % (t, b2h(val)))
659 else:
660 raise RuntimeError(
661 'Unsupported structure "%s" of file "%s"' % (structure, filename))
662 except Exception as e:
Harald Welteb2e4b4a2022-07-19 23:48:45 +0200663 bad_file_str = df_path + "/" + str(filename) + ", " + str(e)
Harald Weltec91085e2022-02-10 18:05:45 +0100664 self._cmd.poutput("# bad file: %s" % bad_file_str)
665 context['ERR'] += 1
666 context['BAD'].append(bad_file_str)
Philipp Maier24f7bd32021-02-25 17:06:18 +0100667
Harald Weltec91085e2022-02-10 18:05:45 +0100668 # When reading the file is done, make sure the parent file is
669 # selected again. This will be the usual case, however we need
670 # to check before since we must not select the same DF twice
Harald Weltea6c0f882022-07-17 14:23:17 +0200671 if df != self._cmd.lchan.selected_file:
672 self._cmd.lchan.select(df.fid or df.aid, self._cmd)
Philipp Maierac34dcc2021-04-01 17:19:05 +0200673
Harald Weltec91085e2022-02-10 18:05:45 +0100674 self._cmd.poutput("#")
Philipp Maier24f7bd32021-02-25 17:06:18 +0100675
Harald Weltec91085e2022-02-10 18:05:45 +0100676 export_parser = argparse.ArgumentParser()
677 export_parser.add_argument(
678 '--filename', type=str, default=None, help='only export specific file')
Harald Welte08b11ab2022-02-10 18:56:41 +0100679 export_parser.add_argument(
680 '--json', action='store_true', help='export as JSON (less reliable)')
Philipp Maier24f7bd32021-02-25 17:06:18 +0100681
Harald Weltec91085e2022-02-10 18:05:45 +0100682 @cmd2.with_argparser(export_parser)
683 def do_export(self, opts):
684 """Export files to script that can be imported back later"""
685 context = {'ERR': 0, 'COUNT': 0, 'BAD': [],
686 'DF_SKIP': 0, 'DF_SKIP_REASON': []}
Philipp Maier9a4091d2022-05-19 10:20:30 +0200687 kwargs_export = {'as_json': opts.json}
Philipp Maierf16ac6a2022-05-31 14:08:47 +0200688 exception_str_add = ""
689
Harald Weltec91085e2022-02-10 18:05:45 +0100690 if opts.filename:
Philipp Maier7b138b02022-05-31 13:42:56 +0200691 self.export_ef(opts.filename, context, **kwargs_export)
Harald Weltec91085e2022-02-10 18:05:45 +0100692 else:
Philipp Maierf16ac6a2022-05-31 14:08:47 +0200693 try:
694 self.walk(0, self.export_ef, None, context, **kwargs_export)
695 except Exception as e:
696 print("# Stopping early here due to exception: " + str(e))
697 print("#")
698 exception_str_add = ", also had to stop early due to exception:" + str(e)
Philipp Maier80ce71f2021-04-19 21:24:23 +0200699
Harald Weltec91085e2022-02-10 18:05:45 +0100700 self._cmd.poutput(boxed_heading_str("Export summary"))
Philipp Maier80ce71f2021-04-19 21:24:23 +0200701
Harald Weltec91085e2022-02-10 18:05:45 +0100702 self._cmd.poutput("# total files visited: %u" % context['COUNT'])
703 self._cmd.poutput("# bad files: %u" % context['ERR'])
704 for b in context['BAD']:
705 self._cmd.poutput("# " + b)
Philipp Maierf408a402021-04-09 21:16:12 +0200706
Harald Weltec91085e2022-02-10 18:05:45 +0100707 self._cmd.poutput("# skipped dedicated files(s): %u" %
708 context['DF_SKIP'])
709 for b in context['DF_SKIP_REASON']:
710 self._cmd.poutput("# " + b)
Philipp Maierf408a402021-04-09 21:16:12 +0200711
Harald Weltec91085e2022-02-10 18:05:45 +0100712 if context['ERR'] and context['DF_SKIP']:
Philipp Maierf16ac6a2022-05-31 14:08:47 +0200713 raise RuntimeError("unable to export %i elementary file(s) and %i dedicated file(s)%s" % (
714 context['ERR'], context['DF_SKIP'], exception_str_add))
Harald Weltec91085e2022-02-10 18:05:45 +0100715 elif context['ERR']:
716 raise RuntimeError(
Philipp Maierf16ac6a2022-05-31 14:08:47 +0200717 "unable to export %i elementary file(s)%s" % (context['ERR'], exception_str_add))
Harald Weltec91085e2022-02-10 18:05:45 +0100718 elif context['DF_SKIP']:
719 raise RuntimeError(
Philipp Maierf16ac6a2022-05-31 14:08:47 +0200720 "unable to export %i dedicated files(s)%s" % (context['ERR'], exception_str_add))
Harald Welteb2edd142021-01-08 23:29:35 +0100721
Harald Weltec91085e2022-02-10 18:05:45 +0100722 def do_desc(self, opts):
723 """Display human readable file description for the currently selected file"""
Harald Weltea6c0f882022-07-17 14:23:17 +0200724 desc = self._cmd.lchan.selected_file.desc
Harald Weltec91085e2022-02-10 18:05:45 +0100725 if desc:
726 self._cmd.poutput(desc)
727 else:
728 self._cmd.poutput("no description available")
Philipp Maiera8c9ea92021-09-16 12:51:46 +0200729
Harald Welte469db932023-11-01 23:17:06 +0100730 verify_adm_parser = argparse.ArgumentParser()
Harald Weltef9ea63e2023-11-01 23:35:31 +0100731 verify_adm_parser.add_argument('ADM1', nargs='?', type=is_hexstr_or_decimal,
Harald Welte469db932023-11-01 23:17:06 +0100732 help='ADM1 pin value. If none given, CSV file will be queried')
733
734 @cmd2.with_argparser(verify_adm_parser)
735 def do_verify_adm(self, opts):
736 """Verify the ADM (Administrator) PIN specified as argument. This is typically needed in order
737to get write/update permissions to most of the files on SIM cards.
738
739Currently only ADM1 is supported."""
740 if opts.ADM1:
Harald Weltec91085e2022-02-10 18:05:45 +0100741 # use specified ADM-PIN
Harald Welte469db932023-11-01 23:17:06 +0100742 pin_adm = sanitize_pin_adm(opts.ADM1)
Harald Weltec91085e2022-02-10 18:05:45 +0100743 else:
744 # try to find an ADM-PIN if none is specified
745 result = card_key_provider_get_field(
746 'ADM1', key='ICCID', value=self._cmd.iccid)
747 pin_adm = sanitize_pin_adm(result)
748 if pin_adm:
749 self._cmd.poutput(
750 "found ADM-PIN '%s' for ICCID '%s'" % (result, self._cmd.iccid))
751 else:
752 raise ValueError(
753 "cannot find ADM-PIN for ICCID '%s'" % (self._cmd.iccid))
Philipp Maiera8c9ea92021-09-16 12:51:46 +0200754
Harald Weltec91085e2022-02-10 18:05:45 +0100755 if pin_adm:
Harald Welte46255122023-10-21 23:40:42 +0200756 self._cmd.lchan.scc.verify_chv(self._cmd.card._adm_chv_num, h2b(pin_adm))
Harald Weltec91085e2022-02-10 18:05:45 +0100757 else:
758 raise ValueError("error: cannot authenticate, no adm-pin!")
759
Philipp Maier7b9e2442023-03-22 15:19:54 +0100760 def do_cardinfo(self, opts):
761 """Display information about the currently inserted card"""
762 self._cmd.poutput("Card info:")
763 self._cmd.poutput(" Name: %s" % self._cmd.card.name)
Harald Welte46255122023-10-21 23:40:42 +0200764 self._cmd.poutput(" ATR: %s" % b2h(self._cmd.lchan.scc.get_atr()))
Philipp Maier7b9e2442023-03-22 15:19:54 +0100765 self._cmd.poutput(" ICCID: %s" % self._cmd.iccid)
Harald Welte46255122023-10-21 23:40:42 +0200766 self._cmd.poutput(" Class-Byte: %s" % self._cmd.lchan.scc.cla_byte)
767 self._cmd.poutput(" Select-Ctrl: %s" % self._cmd.lchan.scc.sel_ctrl)
Philipp Maier7b9e2442023-03-22 15:19:54 +0100768 self._cmd.poutput(" AIDs:")
769 for a in self._cmd.rs.mf.applications:
770 self._cmd.poutput(" %s" % a)
Harald Welteb2edd142021-01-08 23:29:35 +0100771
Harald Welte31d2cf02021-04-03 10:47:29 +0200772@with_default_category('ISO7816 Commands')
773class Iso7816Commands(CommandSet):
Harald Weltec91085e2022-02-10 18:05:45 +0100774 def __init__(self):
775 super().__init__()
Harald Welte31d2cf02021-04-03 10:47:29 +0200776
Harald Weltec91085e2022-02-10 18:05:45 +0100777 def do_select(self, opts):
778 """SELECT a File (ADF/DF/EF)"""
779 if len(opts.arg_list) == 0:
Harald Welteb2e4b4a2022-07-19 23:48:45 +0200780 path = self._cmd.lchan.selected_file.fully_qualified_path_str(True)
781 path_fid = self._cmd.lchan.selected_file.fully_qualified_path_str(False)
782 self._cmd.poutput("currently selected file: %s (%s)" % (path, path_fid))
Harald Weltec91085e2022-02-10 18:05:45 +0100783 return
Harald Welte31d2cf02021-04-03 10:47:29 +0200784
Harald Weltec91085e2022-02-10 18:05:45 +0100785 path = opts.arg_list[0]
Harald Weltea6c0f882022-07-17 14:23:17 +0200786 fcp_dec = self._cmd.lchan.select(path, self._cmd)
Harald Weltec91085e2022-02-10 18:05:45 +0100787 self._cmd.update_prompt()
788 self._cmd.poutput_json(fcp_dec)
Harald Welte31d2cf02021-04-03 10:47:29 +0200789
Harald Weltec91085e2022-02-10 18:05:45 +0100790 def complete_select(self, text, line, begidx, endidx) -> List[str]:
791 """Command Line tab completion for SELECT"""
Harald Weltea6c0f882022-07-17 14:23:17 +0200792 index_dict = {1: self._cmd.lchan.selected_file.get_selectable_names()}
Harald Weltec91085e2022-02-10 18:05:45 +0100793 return self._cmd.index_based_complete(text, line, begidx, endidx, index_dict=index_dict)
Harald Welte31d2cf02021-04-03 10:47:29 +0200794
Harald Weltec91085e2022-02-10 18:05:45 +0100795 def get_code(self, code):
796 """Use code either directly or try to get it from external data source"""
797 auto = ('PIN1', 'PIN2', 'PUK1', 'PUK2')
Harald Welte31d2cf02021-04-03 10:47:29 +0200798
Harald Weltec91085e2022-02-10 18:05:45 +0100799 if str(code).upper() not in auto:
800 return sanitize_pin_adm(code)
Harald Welte31d2cf02021-04-03 10:47:29 +0200801
Harald Weltec91085e2022-02-10 18:05:45 +0100802 result = card_key_provider_get_field(
803 str(code), key='ICCID', value=self._cmd.iccid)
804 result = sanitize_pin_adm(result)
805 if result:
806 self._cmd.poutput("found %s '%s' for ICCID '%s'" %
807 (code.upper(), result, self._cmd.iccid))
808 else:
809 self._cmd.poutput("cannot find %s for ICCID '%s'" %
810 (code.upper(), self._cmd.iccid))
811 return result
Harald Welte31d2cf02021-04-03 10:47:29 +0200812
Harald Weltec91085e2022-02-10 18:05:45 +0100813 verify_chv_parser = argparse.ArgumentParser()
814 verify_chv_parser.add_argument(
815 '--pin-nr', type=int, default=1, help='PIN Number, 1=PIN1, 2=PIN2 or custom value (decimal)')
816 verify_chv_parser.add_argument(
Harald Welte1c849f82023-11-01 23:48:28 +0100817 'pin_code', type=is_decimal, help='PIN code digits, \"PIN1\" or \"PIN2\" to get PIN code from external data source')
Harald Welte31d2cf02021-04-03 10:47:29 +0200818
Harald Weltec91085e2022-02-10 18:05:45 +0100819 @cmd2.with_argparser(verify_chv_parser)
820 def do_verify_chv(self, opts):
Harald Welte12af7932022-02-15 16:39:08 +0100821 """Verify (authenticate) using specified CHV (PIN) code, which is how the specifications
822 call it if you authenticate yourself using the specified PIN. There usually is at least PIN1 and
823 PIN2."""
Harald Weltec91085e2022-02-10 18:05:45 +0100824 pin = self.get_code(opts.pin_code)
Harald Welte46255122023-10-21 23:40:42 +0200825 (data, sw) = self._cmd.lchan.scc.verify_chv(opts.pin_nr, h2b(pin))
Harald Weltec91085e2022-02-10 18:05:45 +0100826 self._cmd.poutput("CHV verification successful")
Harald Welte31d2cf02021-04-03 10:47:29 +0200827
Harald Weltec91085e2022-02-10 18:05:45 +0100828 unblock_chv_parser = argparse.ArgumentParser()
829 unblock_chv_parser.add_argument(
830 '--pin-nr', type=int, default=1, help='PUK Number, 1=PIN1, 2=PIN2 or custom value (decimal)')
831 unblock_chv_parser.add_argument(
Harald Welte1c849f82023-11-01 23:48:28 +0100832 'puk_code', type=is_decimal, help='PUK code digits \"PUK1\" or \"PUK2\" to get PUK code from external data source')
Harald Weltec91085e2022-02-10 18:05:45 +0100833 unblock_chv_parser.add_argument(
Harald Welte1c849f82023-11-01 23:48:28 +0100834 'new_pin_code', type=is_decimal, help='PIN code digits \"PIN1\" or \"PIN2\" to get PIN code from external data source')
Harald Welte31d2cf02021-04-03 10:47:29 +0200835
Harald Weltec91085e2022-02-10 18:05:45 +0100836 @cmd2.with_argparser(unblock_chv_parser)
837 def do_unblock_chv(self, opts):
838 """Unblock PIN code using specified PUK code"""
839 new_pin = self.get_code(opts.new_pin_code)
840 puk = self.get_code(opts.puk_code)
Harald Welte46255122023-10-21 23:40:42 +0200841 (data, sw) = self._cmd.lchan.scc.unblock_chv(
Harald Weltec91085e2022-02-10 18:05:45 +0100842 opts.pin_nr, h2b(puk), h2b(new_pin))
843 self._cmd.poutput("CHV unblock successful")
Harald Welte31d2cf02021-04-03 10:47:29 +0200844
Harald Weltec91085e2022-02-10 18:05:45 +0100845 change_chv_parser = argparse.ArgumentParser()
846 change_chv_parser.add_argument(
847 '--pin-nr', type=int, default=1, help='PUK Number, 1=PIN1, 2=PIN2 or custom value (decimal)')
848 change_chv_parser.add_argument(
Harald Welte1c849f82023-11-01 23:48:28 +0100849 'pin_code', type=is_decimal, help='PIN code digits \"PIN1\" or \"PIN2\" to get PIN code from external data source')
Harald Weltec91085e2022-02-10 18:05:45 +0100850 change_chv_parser.add_argument(
Harald Welte1c849f82023-11-01 23:48:28 +0100851 'new_pin_code', type=is_decimal, help='PIN code digits \"PIN1\" or \"PIN2\" to get PIN code from external data source')
Harald Welte31d2cf02021-04-03 10:47:29 +0200852
Harald Weltec91085e2022-02-10 18:05:45 +0100853 @cmd2.with_argparser(change_chv_parser)
854 def do_change_chv(self, opts):
855 """Change PIN code to a new PIN code"""
856 new_pin = self.get_code(opts.new_pin_code)
857 pin = self.get_code(opts.pin_code)
Harald Welte46255122023-10-21 23:40:42 +0200858 (data, sw) = self._cmd.lchan.scc.change_chv(
Harald Weltec91085e2022-02-10 18:05:45 +0100859 opts.pin_nr, h2b(pin), h2b(new_pin))
860 self._cmd.poutput("CHV change successful")
Harald Welte31d2cf02021-04-03 10:47:29 +0200861
Harald Weltec91085e2022-02-10 18:05:45 +0100862 disable_chv_parser = argparse.ArgumentParser()
863 disable_chv_parser.add_argument(
864 '--pin-nr', type=int, default=1, help='PIN Number, 1=PIN1, 2=PIN2 or custom value (decimal)')
865 disable_chv_parser.add_argument(
Harald Welte1c849f82023-11-01 23:48:28 +0100866 'pin_code', type=is_decimal, help='PIN code digits, \"PIN1\" or \"PIN2\" to get PIN code from external data source')
Harald Welte31d2cf02021-04-03 10:47:29 +0200867
Harald Weltec91085e2022-02-10 18:05:45 +0100868 @cmd2.with_argparser(disable_chv_parser)
869 def do_disable_chv(self, opts):
870 """Disable PIN code using specified PIN code"""
871 pin = self.get_code(opts.pin_code)
Harald Welte46255122023-10-21 23:40:42 +0200872 (data, sw) = self._cmd.lchan.scc.disable_chv(opts.pin_nr, h2b(pin))
Harald Weltec91085e2022-02-10 18:05:45 +0100873 self._cmd.poutput("CHV disable successful")
Harald Welte31d2cf02021-04-03 10:47:29 +0200874
Harald Weltec91085e2022-02-10 18:05:45 +0100875 enable_chv_parser = argparse.ArgumentParser()
876 enable_chv_parser.add_argument(
877 '--pin-nr', type=int, default=1, help='PIN Number, 1=PIN1, 2=PIN2 or custom value (decimal)')
878 enable_chv_parser.add_argument(
Harald Welte1c849f82023-11-01 23:48:28 +0100879 'pin_code', type=is_decimal, help='PIN code digits, \"PIN1\" or \"PIN2\" to get PIN code from external data source')
Harald Welte31d2cf02021-04-03 10:47:29 +0200880
Harald Weltec91085e2022-02-10 18:05:45 +0100881 @cmd2.with_argparser(enable_chv_parser)
882 def do_enable_chv(self, opts):
883 """Enable PIN code using specified PIN code"""
884 pin = self.get_code(opts.pin_code)
Harald Welte46255122023-10-21 23:40:42 +0200885 (data, sw) = self._cmd.lchan.scc.enable_chv(opts.pin_nr, h2b(pin))
Harald Weltec91085e2022-02-10 18:05:45 +0100886 self._cmd.poutput("CHV enable successful")
Harald Welte31d2cf02021-04-03 10:47:29 +0200887
Harald Weltec91085e2022-02-10 18:05:45 +0100888 def do_deactivate_file(self, opts):
Harald Welte799c3542022-02-15 15:56:28 +0100889 """Deactivate the currently selected EF"""
Harald Welte46255122023-10-21 23:40:42 +0200890 (data, sw) = self._cmd.lchan.scc.deactivate_file()
Harald Weltea4631612021-04-10 18:17:55 +0200891
Harald Welte799c3542022-02-15 15:56:28 +0100892 activate_file_parser = argparse.ArgumentParser()
893 activate_file_parser.add_argument('NAME', type=str, help='File name or FID of file to activate')
894 @cmd2.with_argparser(activate_file_parser)
Harald Weltec91085e2022-02-10 18:05:45 +0100895 def do_activate_file(self, opts):
Harald Weltecd18ed02024-01-15 09:21:03 +0100896 """Activate the specified EF by sending an ACTIVATE FILE apdu command (used to be called REHABILITATE
897in TS 11.11 for classic SIM).
898
899This command is used to (re-)activate a file that is currently in deactivated (sometimes also called
900"invalidated") state. You need to call this from the DF above the to-be-activated EF and specify the name or
901FID of the file to activate.
902
903Note that for *deactivation* the to-be-deactivated EF must be selected, but for *activation*, the DF
904above the to-be-activated EF must be selected!"""
Harald Weltea6c0f882022-07-17 14:23:17 +0200905 (data, sw) = self._cmd.lchan.activate_file(opts.NAME)
Harald Welte485692b2021-05-25 22:21:44 +0200906
Harald Weltec91085e2022-02-10 18:05:45 +0100907 def complete_activate_file(self, text, line, begidx, endidx) -> List[str]:
908 """Command Line tab completion for ACTIVATE FILE"""
Harald Weltea6c0f882022-07-17 14:23:17 +0200909 index_dict = {1: self._cmd.lchan.selected_file.get_selectable_names()}
Harald Weltec91085e2022-02-10 18:05:45 +0100910 return self._cmd.index_based_complete(text, line, begidx, endidx, index_dict=index_dict)
Harald Welte31d2cf02021-04-03 10:47:29 +0200911
Harald Weltec91085e2022-02-10 18:05:45 +0100912 open_chan_parser = argparse.ArgumentParser()
913 open_chan_parser.add_argument(
914 'chan_nr', type=int, default=0, help='Channel Number')
Harald Welte703f9332021-04-10 18:39:32 +0200915
Harald Weltec91085e2022-02-10 18:05:45 +0100916 @cmd2.with_argparser(open_chan_parser)
917 def do_open_channel(self, opts):
918 """Open a logical channel."""
Harald Welte46255122023-10-21 23:40:42 +0200919 (data, sw) = self._cmd.lchan.scc.manage_channel(
Harald Weltec91085e2022-02-10 18:05:45 +0100920 mode='open', lchan_nr=opts.chan_nr)
Harald Weltebdf59572023-10-21 20:06:19 +0200921 # this is executed only in successful case, as unsuccessful raises exception
922 self._cmd.lchan.add_lchan(opts.chan_nr)
Harald Welte703f9332021-04-10 18:39:32 +0200923
Harald Weltec91085e2022-02-10 18:05:45 +0100924 close_chan_parser = argparse.ArgumentParser()
925 close_chan_parser.add_argument(
926 'chan_nr', type=int, default=0, help='Channel Number')
Harald Welte703f9332021-04-10 18:39:32 +0200927
Harald Weltec91085e2022-02-10 18:05:45 +0100928 @cmd2.with_argparser(close_chan_parser)
929 def do_close_channel(self, opts):
930 """Close a logical channel."""
Harald Welte46255122023-10-21 23:40:42 +0200931 (data, sw) = self._cmd.lchan.scc.manage_channel(
Harald Weltec91085e2022-02-10 18:05:45 +0100932 mode='close', lchan_nr=opts.chan_nr)
Harald Weltebdf59572023-10-21 20:06:19 +0200933 # this is executed only in successful case, as unsuccessful raises exception
934 self._cmd.rs.del_lchan(opts.chan_nr)
Harald Welte703f9332021-04-10 18:39:32 +0200935
Harald Welte20650992023-10-21 23:47:02 +0200936 switch_chan_parser = argparse.ArgumentParser()
937 switch_chan_parser.add_argument(
938 'chan_nr', type=int, default=0, help='Channel Number')
939
940 @cmd2.with_argparser(switch_chan_parser)
941 def do_switch_channel(self, opts):
942 """Switch currently active logical channel."""
943 self._cmd.lchan._select_pre(self._cmd)
944 self._cmd.lchan = self._cmd.rs.lchan[opts.chan_nr]
945 self._cmd.lchan._select_post(self._cmd)
946 self._cmd.update_prompt()
947
Harald Weltec91085e2022-02-10 18:05:45 +0100948 def do_status(self, opts):
949 """Perform the STATUS command."""
Harald Weltea6c0f882022-07-17 14:23:17 +0200950 fcp_dec = self._cmd.lchan.status()
Harald Weltec91085e2022-02-10 18:05:45 +0100951 self._cmd.poutput_json(fcp_dec)
Harald Welte34b05d32021-05-25 22:03:13 +0200952
Harald Welteec950532021-10-20 13:09:00 +0200953
Harald Weltecab26c72022-08-06 16:12:30 +0200954class Proact(ProactiveHandler):
955 def receive_fetch(self, pcmd: ProactiveCommand):
956 # print its parsed representation
957 print(pcmd.decoded)
958 # TODO: implement the basics, such as SMS Sending, ...
959
960
Harald Welte703f9332021-04-10 18:39:32 +0200961
Harald Weltef422eb12023-06-09 11:15:09 +0200962option_parser = argparse.ArgumentParser(description='interactive SIM card shell',
Harald Weltef2e761c2021-04-11 11:56:44 +0200963 formatter_class=argparse.ArgumentDefaultsHelpFormatter)
Harald Welte28c24312021-04-11 12:19:36 +0200964argparse_add_reader_args(option_parser)
Harald Weltec8ff0262021-04-11 12:06:13 +0200965
966global_group = option_parser.add_argument_group('General Options')
967global_group.add_argument('--script', metavar='PATH', default=None,
Harald Welte28c24312021-04-11 12:19:36 +0200968 help='script with pySim-shell commands to be executed automatically at start-up')
Harald Weltec91085e2022-02-10 18:05:45 +0100969global_group.add_argument('--csv', metavar='FILE',
970 default=None, help='Read card data from CSV file')
Philipp Maier76667642021-09-22 16:53:22 +0200971global_group.add_argument("--card_handler", dest="card_handler_config", metavar="FILE",
Harald Weltec91085e2022-02-10 18:05:45 +0100972 help="Use automatic card handling machine")
Harald Weltec8ff0262021-04-11 12:06:13 +0200973
974adm_group = global_group.add_mutually_exclusive_group()
975adm_group.add_argument('-a', '--pin-adm', metavar='PIN_ADM1', dest='pin_adm', default=None,
976 help='ADM PIN used for provisioning (overwrites default)')
977adm_group.add_argument('-A', '--pin-adm-hex', metavar='PIN_ADM1_HEX', dest='pin_adm_hex', default=None,
978 help='ADM PIN used for provisioning, as hex string (16 characters long)')
Harald Welteb2edd142021-01-08 23:29:35 +0100979
Harald Welte38306df2023-07-11 21:17:55 +0200980option_parser.add_argument("command", nargs='?',
981 help="A pySim-shell command that would optionally be executed at startup")
982option_parser.add_argument('command_args', nargs=argparse.REMAINDER,
983 help="Optional Arguments for command")
984
Harald Welteb2edd142021-01-08 23:29:35 +0100985
986if __name__ == '__main__':
987
Harald Weltec91085e2022-02-10 18:05:45 +0100988 # Parse options
989 opts = option_parser.parse_args()
Harald Welteb2edd142021-01-08 23:29:35 +0100990
Harald Weltec91085e2022-02-10 18:05:45 +0100991 # If a script file is specified, be sure that it actually exists
992 if opts.script:
993 if not os.access(opts.script, os.R_OK):
994 print("Invalid script file!")
995 sys.exit(2)
Philipp Maier13e258d2021-04-08 17:48:49 +0200996
Harald Weltec91085e2022-02-10 18:05:45 +0100997 # Register csv-file as card data provider, either from specified CSV
998 # or from CSV file in home directory
999 csv_default = str(Path.home()) + "/.osmocom/pysim/card_data.csv"
1000 if opts.csv:
1001 card_key_provider_register(CardKeyProviderCsv(opts.csv))
1002 if os.path.isfile(csv_default):
1003 card_key_provider_register(CardKeyProviderCsv(csv_default))
Philipp Maier2b11c322021-03-17 12:37:39 +01001004
Harald Weltec91085e2022-02-10 18:05:45 +01001005 # Init card reader driver
Harald Weltecab26c72022-08-06 16:12:30 +02001006 sl = init_reader(opts, proactive_handler = Proact())
Philipp Maierea95c392021-09-16 13:10:19 +02001007
Harald Weltec91085e2022-02-10 18:05:45 +01001008 # Create a card handler (for bulk provisioning)
1009 if opts.card_handler_config:
1010 ch = CardHandlerAuto(None, opts.card_handler_config)
1011 else:
1012 ch = CardHandler(sl)
Philipp Maier76667642021-09-22 16:53:22 +02001013
Harald Weltec91085e2022-02-10 18:05:45 +01001014 # Detect and initialize the card in the reader. This may fail when there
1015 # is no card in the reader or the card is unresponsive. PysimApp is
1016 # able to tolerate and recover from that.
1017 try:
1018 rs, card = init_card(sl)
1019 app = PysimApp(card, rs, sl, ch, opts.script)
1020 except:
Philipp Maier6bfa8a82023-10-09 13:32:49 +02001021 print("Card initialization (%s) failed with an exception:" % str(sl))
Harald Weltec91085e2022-02-10 18:05:45 +01001022 print("---------------------8<---------------------")
1023 traceback.print_exc()
1024 print("---------------------8<---------------------")
1025 print("(you may still try to recover from this manually by using the 'equip' command.)")
1026 print(
1027 " it should also be noted that some readers may behave strangely when no card")
1028 print(" is inserted.)")
1029 print("")
Philipp Maiereb3b0dd2023-11-23 11:46:39 +01001030 if opts.script:
1031 print("will not execute startup script due to card initialization errors!")
1032 app = PysimApp(None, None, sl, ch)
Philipp Maierea95c392021-09-16 13:10:19 +02001033
Harald Weltec91085e2022-02-10 18:05:45 +01001034 # If the user supplies an ADM PIN at via commandline args authenticate
1035 # immediately so that the user does not have to use the shell commands
1036 pin_adm = sanitize_pin_adm(opts.pin_adm, opts.pin_adm_hex)
1037 if pin_adm:
1038 if not card:
1039 print("Card error, cannot do ADM verification with supplied ADM pin now.")
1040 try:
Philipp Maier4840d4d2023-09-06 14:54:14 +02001041 card._scc.verify_chv(card._adm_chv_num, h2b(pin_adm))
Harald Weltec91085e2022-02-10 18:05:45 +01001042 except Exception as e:
1043 print(e)
Philipp Maier228c98e2021-03-10 20:14:06 +01001044
Harald Welte38306df2023-07-11 21:17:55 +02001045 if opts.command:
1046 app.onecmd_plus_hooks('{} {}'.format(opts.command, ' '.join(opts.command_args)))
1047 else:
1048 app.cmdloop()