blob: bbb14b27a723c3e4a5f2b99dd885fac4c458618b [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 Weltecab26c72022-08-06 16:12:30 +02005# (C) 2021-2022 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 *
51from pySim.commands import SimCardCommands
Harald Weltecab26c72022-08-06 16:12:30 +020052from pySim.transport import init_reader, ApduTracer, argparse_add_reader_args, ProactiveHandler
Harald Weltef8d2e2b2023-07-09 17:58:38 +020053from pySim.cards import card_detect, SimCardBase, UiccCardBase
Philipp Maiere345e112023-06-09 15:19:56 +020054from pySim.utils import h2b, b2h, i2h, swap_nibbles, rpad, JsonEncoder, bertlv_parse_one, sw_match
Philipp Maiera42ee6f2023-08-16 10:44:57 +020055from pySim.utils import sanitize_pin_adm, tabulate_str_list, boxed_heading_str, Hexstr, dec_iccid
Philipp Maier76667642021-09-22 16:53:22 +020056from pySim.card_handler import CardHandler, CardHandlerAuto
Harald Welteb2edd142021-01-08 23:29:35 +010057
Harald Welte531894d2023-07-11 19:11:11 +020058from pySim.filesystem import CardDF, CardADF, CardModel, CardApplication
59from pySim.runtime import RuntimeState
Philipp Maiera028c7d2021-11-08 16:12:03 +010060from pySim.profile import CardProfile
Vadim Yanitskiy87dd0202023-04-22 20:45:29 +070061from pySim.cdma_ruim import CardProfileRUIM
Harald Welteb2edd142021-01-08 23:29:35 +010062from pySim.ts_102_221 import CardProfileUICC
Harald Welte3c9b7842021-10-19 21:44:24 +020063from pySim.ts_102_222 import Ts102222Commands
Harald Welte2a33ad22021-10-20 10:14:18 +020064from pySim.gsm_r import DF_EIRENE
Harald Weltecab26c72022-08-06 16:12:30 +020065from pySim.cat import ProactiveCommand
Harald Welteb2edd142021-01-08 23:29:35 +010066
Harald Welte4c1dca02021-10-14 17:48:25 +020067# we need to import this module so that the SysmocomSJA2 sub-class of
68# CardModel is created, which will add the ATR-based matching and
69# calling of SysmocomSJA2.add_files. See CardModel.apply_matching_models
Harald Weltef44256c2021-10-14 15:53:39 +020070import pySim.sysmocom_sja2
71
Harald Welte6ad9a242023-07-11 18:55:29 +020072# we need to import these modules so that the various sub-classes of
73# CardProfile are created, which will be used in init_card() to iterate
74# over all known CardProfile sub-classes.
75import pySim.ts_31_102
76import pySim.ts_31_103
77import pySim.ts_31_104
78import pySim.ara_m
79import pySim.global_platform
Harald Welte268a2022023-10-22 13:12:11 +020080import pySim.euicc
Harald Welte6ad9a242023-07-11 18:55:29 +020081
Harald Welte4442b3d2021-04-03 09:00:16 +020082from pySim.card_key_provider import CardKeyProviderCsv, card_key_provider_register, card_key_provider_get_field
Philipp Maier2b11c322021-03-17 12:37:39 +010083
Harald Weltec91085e2022-02-10 18:05:45 +010084
Philipp Maierea95c392021-09-16 13:10:19 +020085def init_card(sl):
Harald Weltec91085e2022-02-10 18:05:45 +010086 """
87 Detect card in reader and setup card profile and runtime state. This
88 function must be called at least once on startup. The card and runtime
89 state object (rs) is required for all pySim-shell commands.
90 """
Philipp Maierea95c392021-09-16 13:10:19 +020091
Philipp Maier91c971b2023-10-09 10:48:46 +020092 # Create command layer
93 scc = SimCardCommands(transport=sl)
94
Harald Weltec91085e2022-02-10 18:05:45 +010095 # Wait up to three seconds for a card in reader and try to detect
96 # the card type.
97 print("Waiting for card...")
98 try:
99 sl.wait_for_card(3)
100 except NoCardError:
101 print("No card detected!")
102 return None, None
103 except:
104 print("Card not readable!")
105 return None, None
Philipp Maierea95c392021-09-16 13:10:19 +0200106
Philipp Maier24031252022-06-14 16:16:42 +0200107 generic_card = False
Harald Weltef8d2e2b2023-07-09 17:58:38 +0200108 card = card_detect(scc)
Harald Weltec91085e2022-02-10 18:05:45 +0100109 if card is None:
110 print("Warning: Could not detect card type - assuming a generic card type...")
Harald Weltef8d2e2b2023-07-09 17:58:38 +0200111 card = SimCardBase(scc)
Philipp Maier24031252022-06-14 16:16:42 +0200112 generic_card = True
Philipp Maiera028c7d2021-11-08 16:12:03 +0100113
Harald Weltec91085e2022-02-10 18:05:45 +0100114 profile = CardProfile.pick(scc)
115 if profile is None:
116 print("Unsupported card type!")
Philipp Maier7226c092022-06-01 17:58:38 +0200117 return None, card
Philipp Maierea95c392021-09-16 13:10:19 +0200118
Philipp Maier24031252022-06-14 16:16:42 +0200119 # ETSI TS 102 221, Table 9.3 specifies a default for the PIN key
120 # references, however card manufactures may still decide to pick an
121 # arbitrary key reference. In case we run on a generic card class that is
122 # detected as an UICC, we will pick the key reference that is officially
123 # specified.
124 if generic_card and isinstance(profile, CardProfileUICC):
125 card._adm_chv_num = 0x0A
126
Harald Weltec91085e2022-02-10 18:05:45 +0100127 print("Info: Card is of type: %s" % str(profile))
Philipp Maiera028c7d2021-11-08 16:12:03 +0100128
Harald Welte6ad9a242023-07-11 18:55:29 +0200129 # FIXME: this shouldn't really be here but somewhere else/more generic.
130 # We cannot do it within pySim/profile.py as that would create circular
131 # dependencies between the individual profiles and profile.py.
Harald Weltec91085e2022-02-10 18:05:45 +0100132 if isinstance(profile, CardProfileUICC):
Harald Welte6ad9a242023-07-11 18:55:29 +0200133 for app_cls in CardApplication.__subclasses__():
134 constr_sig = inspect.signature(app_cls.__init__)
135 # skip any intermediary sub-classes such as CardApplicationSD
136 if len(constr_sig.parameters) != 1:
137 continue
138 profile.add_application(app_cls())
Harald Weltef4a01472023-07-04 21:14:23 +0200139 # We have chosen SimCard() above, but we now know it actually is an UICC
140 # so it's safe to assume it supports USIM application (which we're adding above).
141 # IF we don't do this, we will have a SimCard but try USIM specific commands like
142 # the update_ust method (see https://osmocom.org/issues/6055)
143 if generic_card:
Harald Weltef8d2e2b2023-07-09 17:58:38 +0200144 card = UiccCardBase(scc)
Philipp Maiera028c7d2021-11-08 16:12:03 +0100145
Harald Weltec91085e2022-02-10 18:05:45 +0100146 # Create runtime state with card profile
147 rs = RuntimeState(card, profile)
Philipp Maierea95c392021-09-16 13:10:19 +0200148
Harald Weltec91085e2022-02-10 18:05:45 +0100149 CardModel.apply_matching_models(scc, rs)
Harald Weltef44256c2021-10-14 15:53:39 +0200150
Harald Weltec91085e2022-02-10 18:05:45 +0100151 # inform the transport that we can do context-specific SW interpretation
152 sl.set_sw_interpreter(rs)
Philipp Maierea95c392021-09-16 13:10:19 +0200153
Harald Weltec91085e2022-02-10 18:05:45 +0100154 return rs, card
155
Philipp Maier2b11c322021-03-17 12:37:39 +0100156
Harald Weltee1268722023-06-24 07:57:08 +0200157class Cmd2Compat(cmd2.Cmd):
158 """Backwards-compatibility wrapper around cmd2.Cmd to support older and newer
159 releases. See https://github.com/python-cmd2/cmd2/blob/master/CHANGELOG.md"""
160 def run_editor(self, file_path: Optional[str] = None) -> None:
161 if version.parse(cmd2.__version__) < version.parse("2.0.0"):
Harald Welte0ec01502023-06-24 10:00:12 +0200162 return self._run_editor(file_path) # pylint: disable=no-member
Harald Weltee1268722023-06-24 07:57:08 +0200163 else:
Harald Welte0ec01502023-06-24 10:00:12 +0200164 return super().run_editor(file_path) # pylint: disable=no-member
165
166class Settable2Compat(cmd2.Settable):
167 """Backwards-compatibility wrapper around cmd2.Settable to support older and newer
168 releases. See https://github.com/python-cmd2/cmd2/blob/master/CHANGELOG.md"""
169 def __init__(self, name, val_type, description, settable_object, **kwargs):
170 if version.parse(cmd2.__version__) < version.parse("2.0.0"):
171 super().__init__(name, val_type, description, **kwargs) # pylint: disable=no-value-for-parameter
172 else:
173 super().__init__(name, val_type, description, settable_object, **kwargs) # pylint: disable=too-many-function-args
Harald Weltee1268722023-06-24 07:57:08 +0200174
175class PysimApp(Cmd2Compat):
Harald Weltec91085e2022-02-10 18:05:45 +0100176 CUSTOM_CATEGORY = 'pySim Commands'
Philipp Maier76667642021-09-22 16:53:22 +0200177
Harald Weltec91085e2022-02-10 18:05:45 +0100178 def __init__(self, card, rs, sl, ch, script=None):
Harald Weltec85d4062023-04-27 17:10:17 +0200179 if version.parse(cmd2.__version__) < version.parse("2.0.0"):
180 kwargs = {'use_ipython': True}
181 else:
182 kwargs = {'include_ipy': True}
183
Oliver Smithe47ea5f2023-05-23 12:54:15 +0200184 # pylint: disable=unexpected-keyword-arg
Harald Weltec91085e2022-02-10 18:05:45 +0100185 super().__init__(persistent_history_file='~/.pysim_shell_history', allow_cli_args=False,
Harald Weltec85d4062023-04-27 17:10:17 +0200186 auto_load_commands=False, startup_script=script, **kwargs)
Harald Welte961b8032023-04-27 17:30:22 +0200187 self.intro = style('Welcome to pySim-shell!', fg=RED)
Harald Weltec91085e2022-02-10 18:05:45 +0100188 self.default_category = 'pySim-shell built-in commands'
189 self.card = None
190 self.rs = None
Harald Weltea6c0f882022-07-17 14:23:17 +0200191 self.lchan = None
192 self.py_locals = {'card': self.card, 'rs': self.rs, 'lchan': self.lchan}
Harald Weltec91085e2022-02-10 18:05:45 +0100193 self.sl = sl
194 self.ch = ch
Harald Welte1748b932021-04-06 21:12:25 +0200195
Harald Weltec91085e2022-02-10 18:05:45 +0100196 self.numeric_path = False
Harald Weltec91085e2022-02-10 18:05:45 +0100197 self.conserve_write = True
Harald Weltec91085e2022-02-10 18:05:45 +0100198 self.json_pretty_print = True
Harald Weltec91085e2022-02-10 18:05:45 +0100199 self.apdu_trace = False
Philipp Maier5d698e52021-09-16 13:18:01 +0200200
Harald Welte0ec01502023-06-24 10:00:12 +0200201 self.add_settable(Settable2Compat('numeric_path', bool, 'Print File IDs instead of names', self,
202 onchange_cb=self._onchange_numeric_path))
203 self.add_settable(Settable2Compat('conserve_write', bool, 'Read and compare before write', self,
204 onchange_cb=self._onchange_conserve_write))
205 self.add_settable(Settable2Compat('json_pretty_print', bool, 'Pretty-Print JSON output', self))
206 self.add_settable(Settable2Compat('apdu_trace', bool, 'Trace and display APDUs exchanged with card', self,
207 onchange_cb=self._onchange_apdu_trace))
Harald Weltec91085e2022-02-10 18:05:45 +0100208 self.equip(card, rs)
Philipp Maier5d698e52021-09-16 13:18:01 +0200209
Harald Weltec91085e2022-02-10 18:05:45 +0100210 def equip(self, card, rs):
211 """
212 Equip pySim-shell with the supplied card and runtime state, add (or remove) all required settables and
213 and commands to enable card operations.
214 """
Philipp Maier76667642021-09-22 16:53:22 +0200215
Harald Weltec91085e2022-02-10 18:05:45 +0100216 rc = False
Philipp Maier5d698e52021-09-16 13:18:01 +0200217
Harald Weltec91085e2022-02-10 18:05:45 +0100218 # Unequip everything from pySim-shell that would not work in unequipped state
219 if self.rs:
Harald Weltea6c0f882022-07-17 14:23:17 +0200220 lchan = self.rs.lchan[0]
221 lchan.unregister_cmds(self)
iw0f818acd2023-06-19 10:27:04 +0200222 if self.rs.profile:
223 for cmd_set in self.rs.profile.shell_cmdsets:
224 self.unregister_command_set(cmd_set)
225
Harald Welte892526f2023-06-06 17:09:50 +0200226 for cmds in [Iso7816Commands, Ts102222Commands, PySimCommands]:
Harald Weltec91085e2022-02-10 18:05:45 +0100227 cmd_set = self.find_commandsets(cmds)
228 if cmd_set:
229 self.unregister_command_set(cmd_set[0])
Philipp Maier5d698e52021-09-16 13:18:01 +0200230
Harald Weltec91085e2022-02-10 18:05:45 +0100231 self.card = card
232 self.rs = rs
Philipp Maier5d698e52021-09-16 13:18:01 +0200233
Harald Weltec91085e2022-02-10 18:05:45 +0100234 # When a card object and a runtime state is present, (re)equip pySim-shell with everything that is
235 # needed to operate on cards.
236 if self.card and self.rs:
Harald Weltea6c0f882022-07-17 14:23:17 +0200237 self.lchan = self.rs.lchan[0]
Harald Weltec91085e2022-02-10 18:05:45 +0100238 self._onchange_conserve_write(
239 'conserve_write', False, self.conserve_write)
240 self._onchange_apdu_trace('apdu_trace', False, self.apdu_trace)
Harald Welte659781c2023-06-06 17:00:51 +0200241 if self.rs.profile:
242 for cmd_set in self.rs.profile.shell_cmdsets:
243 self.register_command_set(cmd_set)
Harald Weltec91085e2022-02-10 18:05:45 +0100244 self.register_command_set(Iso7816Commands())
Harald Welte3c9b7842021-10-19 21:44:24 +0200245 self.register_command_set(Ts102222Commands())
Harald Weltec91085e2022-02-10 18:05:45 +0100246 self.register_command_set(PySimCommands())
Philipp Maiera42ee6f2023-08-16 10:44:57 +0200247
Philipp Maier7c0cd0a2023-10-16 15:02:07 +0200248 try:
249 self.lchan.select('MF/EF.ICCID', self)
250 self.iccid = dec_iccid(self.lchan.read_binary()[0])
251 except:
252 self.iccid = None
Philipp Maiera42ee6f2023-08-16 10:44:57 +0200253
Harald Weltea6c0f882022-07-17 14:23:17 +0200254 self.lchan.select('MF', self)
Harald Weltec91085e2022-02-10 18:05:45 +0100255 rc = True
256 else:
257 self.poutput("pySim-shell not equipped!")
Philipp Maier5d698e52021-09-16 13:18:01 +0200258
Harald Weltec91085e2022-02-10 18:05:45 +0100259 self.update_prompt()
260 return rc
Harald Welteb2edd142021-01-08 23:29:35 +0100261
Harald Weltec91085e2022-02-10 18:05:45 +0100262 def poutput_json(self, data, force_no_pretty=False):
263 """like cmd2.poutput() but for a JSON serializable dict."""
264 if force_no_pretty or self.json_pretty_print == False:
265 output = json.dumps(data, cls=JsonEncoder)
266 else:
267 output = json.dumps(data, cls=JsonEncoder, indent=4)
268 self.poutput(output)
Harald Welteb2edd142021-01-08 23:29:35 +0100269
Harald Weltec91085e2022-02-10 18:05:45 +0100270 def _onchange_numeric_path(self, param_name, old, new):
271 self.update_prompt()
Philipp Maier38c74f62021-03-17 17:19:52 +0100272
Harald Weltec91085e2022-02-10 18:05:45 +0100273 def _onchange_conserve_write(self, param_name, old, new):
274 if self.rs:
275 self.rs.conserve_write = new
Harald Welte7829d8a2021-04-10 11:28:53 +0200276
Harald Weltec91085e2022-02-10 18:05:45 +0100277 def _onchange_apdu_trace(self, param_name, old, new):
278 if self.card:
279 if new == True:
280 self.card._scc._tp.apdu_tracer = self.Cmd2ApduTracer(self)
281 else:
282 self.card._scc._tp.apdu_tracer = None
Harald Welte7829d8a2021-04-10 11:28:53 +0200283
Harald Weltec91085e2022-02-10 18:05:45 +0100284 class Cmd2ApduTracer(ApduTracer):
285 def __init__(self, cmd2_app):
Philipp Maier91c971b2023-10-09 10:48:46 +0200286 self.cmd2 = cmd2_app
Harald Welte7829d8a2021-04-10 11:28:53 +0200287
Harald Weltec91085e2022-02-10 18:05:45 +0100288 def trace_response(self, cmd, sw, resp):
289 self.cmd2.poutput("-> %s %s" % (cmd[:10], cmd[10:]))
290 self.cmd2.poutput("<- %s: %s" % (sw, resp))
Harald Welteb2edd142021-01-08 23:29:35 +0100291
Harald Weltec91085e2022-02-10 18:05:45 +0100292 def update_prompt(self):
Harald Weltea6c0f882022-07-17 14:23:17 +0200293 if self.lchan:
Harald Welteb2e4b4a2022-07-19 23:48:45 +0200294 path_str = self.lchan.selected_file.fully_qualified_path_str(not self.numeric_path)
Harald Welte237ddb52023-10-22 10:36:58 +0200295 self.prompt = 'pySIM-shell (%02u:%s)> ' % (self.lchan.lchan_nr, path_str)
Harald Weltec91085e2022-02-10 18:05:45 +0100296 else:
Philipp Maier7226c092022-06-01 17:58:38 +0200297 if self.card:
298 self.prompt = 'pySIM-shell (no card profile)> '
299 else:
300 self.prompt = 'pySIM-shell (no card)> '
Harald Welteb2edd142021-01-08 23:29:35 +0100301
Harald Weltec91085e2022-02-10 18:05:45 +0100302 @cmd2.with_category(CUSTOM_CATEGORY)
303 def do_intro(self, _):
304 """Display the intro banner"""
305 self.poutput(self.intro)
Philipp Maier9764de22021-11-03 10:44:39 +0100306
Harald Weltec91085e2022-02-10 18:05:45 +0100307 def do_eof(self, _: argparse.Namespace) -> bool:
308 self.poutput("")
309 return self.do_quit('')
Philipp Maier5d698e52021-09-16 13:18:01 +0200310
Harald Weltec91085e2022-02-10 18:05:45 +0100311 @cmd2.with_category(CUSTOM_CATEGORY)
312 def do_equip(self, opts):
313 """Equip pySim-shell with card"""
Philipp Maiere6cba762023-08-11 11:23:17 +0200314 if self.rs and self.rs.profile:
Harald Welte659781c2023-06-06 17:00:51 +0200315 for cmd_set in self.rs.profile.shell_cmdsets:
316 self.unregister_command_set(cmd_set)
Philipp Maier91c971b2023-10-09 10:48:46 +0200317 rs, card = init_card(self.sl)
Harald Weltec91085e2022-02-10 18:05:45 +0100318 self.equip(card, rs)
Philipp Maier76667642021-09-22 16:53:22 +0200319
Philipp Maier7226c092022-06-01 17:58:38 +0200320 apdu_cmd_parser = argparse.ArgumentParser()
321 apdu_cmd_parser.add_argument('APDU', type=str, help='APDU as hex string')
Philipp Maiere7d1b672022-06-01 18:05:34 +0200322 apdu_cmd_parser.add_argument('--expect-sw', help='expect a specified status word', type=str, default=None)
Philipp Maier7226c092022-06-01 17:58:38 +0200323
324 @cmd2.with_argparser(apdu_cmd_parser)
325 def do_apdu(self, opts):
326 """Send a raw APDU to the card, and print SW + Response.
327 DANGEROUS: pySim-shell will not know any card state changes, and
328 not continue to work as expected if you e.g. select a different
329 file."""
Harald Welte46255122023-10-21 23:40:42 +0200330 data, sw = self.lchan.scc._tp.send_apdu(opts.APDU)
Philipp Maier7226c092022-06-01 17:58:38 +0200331 if data:
332 self.poutput("SW: %s, RESP: %s" % (sw, data))
333 else:
334 self.poutput("SW: %s" % sw)
Philipp Maiere7d1b672022-06-01 18:05:34 +0200335 if opts.expect_sw:
336 if not sw_match(sw, opts.expect_sw):
337 raise SwMatchError(sw, opts.expect_sw)
Philipp Maier7226c092022-06-01 17:58:38 +0200338
Harald Weltec91085e2022-02-10 18:05:45 +0100339 class InterceptStderr(list):
340 def __init__(self):
341 self._stderr_backup = sys.stderr
Philipp Maier76667642021-09-22 16:53:22 +0200342
Harald Weltec91085e2022-02-10 18:05:45 +0100343 def __enter__(self):
344 self._stringio_stderr = StringIO()
345 sys.stderr = self._stringio_stderr
346 return self
Philipp Maier76667642021-09-22 16:53:22 +0200347
Harald Weltec91085e2022-02-10 18:05:45 +0100348 def __exit__(self, *args):
349 self.stderr = self._stringio_stderr.getvalue().strip()
350 del self._stringio_stderr
351 sys.stderr = self._stderr_backup
Philipp Maier76667642021-09-22 16:53:22 +0200352
Harald Weltec91085e2022-02-10 18:05:45 +0100353 def _show_failure_sign(self):
Harald Welte961b8032023-04-27 17:30:22 +0200354 self.poutput(style(" +-------------+", fg=LIGHT_RED))
355 self.poutput(style(" + ## ## +", fg=LIGHT_RED))
356 self.poutput(style(" + ## ## +", fg=LIGHT_RED))
357 self.poutput(style(" + ### +", fg=LIGHT_RED))
358 self.poutput(style(" + ## ## +", fg=LIGHT_RED))
359 self.poutput(style(" + ## ## +", fg=LIGHT_RED))
360 self.poutput(style(" +-------------+", fg=LIGHT_RED))
Harald Weltec91085e2022-02-10 18:05:45 +0100361 self.poutput("")
Philipp Maier76667642021-09-22 16:53:22 +0200362
Harald Weltec91085e2022-02-10 18:05:45 +0100363 def _show_success_sign(self):
Harald Welte961b8032023-04-27 17:30:22 +0200364 self.poutput(style(" +-------------+", fg=LIGHT_GREEN))
365 self.poutput(style(" + ## +", fg=LIGHT_GREEN))
366 self.poutput(style(" + ## +", fg=LIGHT_GREEN))
367 self.poutput(style(" + # ## +", fg=LIGHT_GREEN))
368 self.poutput(style(" + ## # +", fg=LIGHT_GREEN))
369 self.poutput(style(" + ## +", fg=LIGHT_GREEN))
370 self.poutput(style(" +-------------+", fg=LIGHT_GREEN))
Harald Weltec91085e2022-02-10 18:05:45 +0100371 self.poutput("")
Philipp Maier76667642021-09-22 16:53:22 +0200372
Harald Weltec91085e2022-02-10 18:05:45 +0100373 def _process_card(self, first, script_path):
Philipp Maier76667642021-09-22 16:53:22 +0200374
Harald Weltec91085e2022-02-10 18:05:45 +0100375 # Early phase of card initialzation (this part may fail with an exception)
376 try:
377 rs, card = init_card(self.sl)
378 rc = self.equip(card, rs)
379 except:
380 self.poutput("")
Philipp Maier6bfa8a82023-10-09 13:32:49 +0200381 self.poutput("Card initialization (%s) failed with an exception:" % str(self.sl))
Harald Weltec91085e2022-02-10 18:05:45 +0100382 self.poutput("---------------------8<---------------------")
383 traceback.print_exc()
384 self.poutput("---------------------8<---------------------")
385 self.poutput("")
386 return -1
Philipp Maier76667642021-09-22 16:53:22 +0200387
Harald Weltec91085e2022-02-10 18:05:45 +0100388 # Actual card processing step. This part should never fail with an exception since the cmd2
389 # do_run_script method will catch any exception that might occur during script execution.
390 if rc:
391 self.poutput("")
392 self.poutput("Transcript stdout:")
393 self.poutput("---------------------8<---------------------")
394 with self.InterceptStderr() as logged:
395 self.do_run_script(script_path)
396 self.poutput("---------------------8<---------------------")
Philipp Maier76667642021-09-22 16:53:22 +0200397
Harald Weltec91085e2022-02-10 18:05:45 +0100398 self.poutput("")
399 self.poutput("Transcript stderr:")
400 if logged.stderr:
401 self.poutput("---------------------8<---------------------")
402 self.poutput(logged.stderr)
403 self.poutput("---------------------8<---------------------")
404 else:
405 self.poutput("(none)")
Philipp Maier76667642021-09-22 16:53:22 +0200406
Harald Weltec91085e2022-02-10 18:05:45 +0100407 # Check for exceptions
408 self.poutput("")
409 if "EXCEPTION of type" not in logged.stderr:
410 return 0
Philipp Maier76667642021-09-22 16:53:22 +0200411
Harald Weltec91085e2022-02-10 18:05:45 +0100412 return -1
Philipp Maier76667642021-09-22 16:53:22 +0200413
Harald Weltec91085e2022-02-10 18:05:45 +0100414 bulk_script_parser = argparse.ArgumentParser()
415 bulk_script_parser.add_argument(
416 'script_path', help="path to the script file")
417 bulk_script_parser.add_argument('--halt_on_error', help='stop card handling if an exeption occurs',
418 action='store_true')
419 bulk_script_parser.add_argument('--tries', type=int, default=2,
420 help='how many tries before trying the next card')
421 bulk_script_parser.add_argument('--on_stop_action', type=str, default=None,
422 help='commandline to execute when card handling has stopped')
423 bulk_script_parser.add_argument('--pre_card_action', type=str, default=None,
424 help='commandline to execute before actually talking to the card')
Philipp Maier76667642021-09-22 16:53:22 +0200425
Harald Weltec91085e2022-02-10 18:05:45 +0100426 @cmd2.with_argparser(bulk_script_parser)
427 @cmd2.with_category(CUSTOM_CATEGORY)
428 def do_bulk_script(self, opts):
429 """Run script on multiple cards (bulk provisioning)"""
Philipp Maier76667642021-09-22 16:53:22 +0200430
Harald Weltec91085e2022-02-10 18:05:45 +0100431 # Make sure that the script file exists and that it is readable.
432 if not os.access(opts.script_path, os.R_OK):
433 self.poutput("Invalid script file!")
434 return
Philipp Maier76667642021-09-22 16:53:22 +0200435
Harald Weltec91085e2022-02-10 18:05:45 +0100436 success_count = 0
437 fail_count = 0
Philipp Maier76667642021-09-22 16:53:22 +0200438
Harald Weltec91085e2022-02-10 18:05:45 +0100439 first = True
440 while 1:
441 # TODO: Count consecutive failures, if more than N consecutive failures occur, then stop.
442 # The ratinale is: There may be a problem with the device, we do want to prevent that
443 # all remaining cards are fired to the error bin. This is only relevant for situations
444 # with large stacks, probably we do not need this feature right now.
Philipp Maier76667642021-09-22 16:53:22 +0200445
Harald Weltec91085e2022-02-10 18:05:45 +0100446 try:
447 # In case of failure, try multiple times.
448 for i in range(opts.tries):
449 # fetch card into reader bay
Philipp Maier91c971b2023-10-09 10:48:46 +0200450 self.ch.get(first)
Philipp Maier76667642021-09-22 16:53:22 +0200451
Harald Weltec91085e2022-02-10 18:05:45 +0100452 # if necessary execute an action before we start processing the card
453 if(opts.pre_card_action):
454 os.system(opts.pre_card_action)
Philipp Maier76667642021-09-22 16:53:22 +0200455
Harald Weltec91085e2022-02-10 18:05:45 +0100456 # process the card
457 rc = self._process_card(first, opts.script_path)
458 if rc == 0:
459 success_count = success_count + 1
460 self._show_success_sign()
461 self.poutput("Statistics: success :%i, failure: %i" % (
462 success_count, fail_count))
463 break
464 else:
465 fail_count = fail_count + 1
466 self._show_failure_sign()
467 self.poutput("Statistics: success :%i, failure: %i" % (
468 success_count, fail_count))
Philipp Maier76667642021-09-22 16:53:22 +0200469
Harald Weltec91085e2022-02-10 18:05:45 +0100470 # Depending on success or failure, the card goes either in the "error" bin or in the
471 # "done" bin.
472 if rc < 0:
Philipp Maier91c971b2023-10-09 10:48:46 +0200473 self.ch.error()
Harald Weltec91085e2022-02-10 18:05:45 +0100474 else:
Philipp Maier91c971b2023-10-09 10:48:46 +0200475 self.ch.done()
Philipp Maier76667642021-09-22 16:53:22 +0200476
Harald Weltec91085e2022-02-10 18:05:45 +0100477 # In most cases it is possible to proceed with the next card, but the
478 # user may decide to halt immediately when an error occurs
479 if opts.halt_on_error and rc < 0:
480 return
Philipp Maier76667642021-09-22 16:53:22 +0200481
Harald Weltec91085e2022-02-10 18:05:45 +0100482 except (KeyboardInterrupt):
483 self.poutput("")
484 self.poutput("Terminated by user!")
485 return
486 except (SystemExit):
487 # When all cards are processed the card handler device will throw a SystemExit
488 # exception. Also Errors that are not recoverable (cards stuck etc.) will end up here.
489 # The user has the option to execute some action to make aware that the card handler
490 # needs service.
491 if(opts.on_stop_action):
492 os.system(opts.on_stop_action)
493 return
494 except:
495 self.poutput("")
Philipp Maier6bfa8a82023-10-09 13:32:49 +0200496 self.poutput("Card handling (%s) failed with an exception:" % str(self.sl))
Harald Weltec91085e2022-02-10 18:05:45 +0100497 self.poutput("---------------------8<---------------------")
498 traceback.print_exc()
499 self.poutput("---------------------8<---------------------")
500 self.poutput("")
501 fail_count = fail_count + 1
502 self._show_failure_sign()
503 self.poutput("Statistics: success :%i, failure: %i" %
504 (success_count, fail_count))
Philipp Maierb52feed2021-09-22 16:43:13 +0200505
Harald Weltec91085e2022-02-10 18:05:45 +0100506 first = False
507
508 echo_parser = argparse.ArgumentParser()
509 echo_parser.add_argument('string', help="string to echo on the shell")
510
511 @cmd2.with_argparser(echo_parser)
512 @cmd2.with_category(CUSTOM_CATEGORY)
513 def do_echo(self, opts):
514 """Echo (print) a string on the console"""
515 self.poutput(opts.string)
516
Harald Weltefc315482022-07-23 12:49:14 +0200517 @cmd2.with_category(CUSTOM_CATEGORY)
518 def do_version(self, opts):
519 """Print the pySim software version."""
520 import pkg_resources
521 self.poutput(pkg_resources.get_distribution('pySim'))
Harald Welteb2edd142021-01-08 23:29:35 +0100522
Harald Welte31d2cf02021-04-03 10:47:29 +0200523@with_default_category('pySim Commands')
524class PySimCommands(CommandSet):
Harald Weltec91085e2022-02-10 18:05:45 +0100525 def __init__(self):
526 super().__init__()
Harald Welteb2edd142021-01-08 23:29:35 +0100527
Harald Weltec91085e2022-02-10 18:05:45 +0100528 dir_parser = argparse.ArgumentParser()
529 dir_parser.add_argument(
530 '--fids', help='Show file identifiers', action='store_true')
531 dir_parser.add_argument(
532 '--names', help='Show file names', action='store_true')
533 dir_parser.add_argument(
534 '--apps', help='Show applications', action='store_true')
535 dir_parser.add_argument(
536 '--all', help='Show all selectable identifiers and names', action='store_true')
Philipp Maier5d3e2592021-02-22 17:22:16 +0100537
Harald Weltec91085e2022-02-10 18:05:45 +0100538 @cmd2.with_argparser(dir_parser)
539 def do_dir(self, opts):
540 """Show a listing of files available in currently selected DF or MF"""
541 if opts.all:
542 flags = []
543 elif opts.fids or opts.names or opts.apps:
544 flags = ['PARENT', 'SELF']
545 if opts.fids:
546 flags += ['FIDS', 'AIDS']
547 if opts.names:
548 flags += ['FNAMES', 'ANAMES']
549 if opts.apps:
550 flags += ['ANAMES', 'AIDS']
551 else:
552 flags = ['PARENT', 'SELF', 'FNAMES', 'ANAMES']
553 selectables = list(
Harald Weltea6c0f882022-07-17 14:23:17 +0200554 self._cmd.lchan.selected_file.get_selectable_names(flags=flags))
Harald Weltec91085e2022-02-10 18:05:45 +0100555 directory_str = tabulate_str_list(
556 selectables, width=79, hspace=2, lspace=1, align_left=True)
Harald Welteb2e4b4a2022-07-19 23:48:45 +0200557 path = self._cmd.lchan.selected_file.fully_qualified_path_str(True)
558 self._cmd.poutput(path)
559 path = self._cmd.lchan.selected_file.fully_qualified_path_str(False)
560 self._cmd.poutput(path)
Harald Weltec91085e2022-02-10 18:05:45 +0100561 self._cmd.poutput(directory_str)
562 self._cmd.poutput("%d files" % len(selectables))
Harald Welteb2edd142021-01-08 23:29:35 +0100563
Philipp Maier7b138b02022-05-31 13:42:56 +0200564 def walk(self, indent=0, action_ef=None, action_df=None, context=None, **kwargs):
Harald Weltec91085e2022-02-10 18:05:45 +0100565 """Recursively walk through the file system, starting at the currently selected DF"""
Philipp Maier7b138b02022-05-31 13:42:56 +0200566
Harald Weltea6c0f882022-07-17 14:23:17 +0200567 if isinstance(self._cmd.lchan.selected_file, CardDF):
Philipp Maier7b138b02022-05-31 13:42:56 +0200568 if action_df:
Philipp Maier91c971b2023-10-09 10:48:46 +0200569 action_df(context, **kwargs)
Philipp Maier7b138b02022-05-31 13:42:56 +0200570
Harald Weltea6c0f882022-07-17 14:23:17 +0200571 files = self._cmd.lchan.selected_file.get_selectables(
Harald Weltec91085e2022-02-10 18:05:45 +0100572 flags=['FNAMES', 'ANAMES'])
573 for f in files:
Philipp Maier7b138b02022-05-31 13:42:56 +0200574 # special case: When no action is performed, just output a directory
575 if not action_ef and not action_df:
Harald Weltec91085e2022-02-10 18:05:45 +0100576 output_str = " " * indent + str(f) + (" " * 250)
577 output_str = output_str[0:25]
578 if isinstance(files[f], CardADF):
579 output_str += " " + str(files[f].aid)
580 else:
581 output_str += " " + str(files[f].fid)
582 output_str += " " + str(files[f].desc)
583 self._cmd.poutput(output_str)
Philipp Maierf408a402021-04-09 21:16:12 +0200584
Harald Weltec91085e2022-02-10 18:05:45 +0100585 if isinstance(files[f], CardDF):
586 skip_df = False
587 try:
Harald Weltea6c0f882022-07-17 14:23:17 +0200588 fcp_dec = self._cmd.lchan.select(f, self._cmd)
Harald Weltec91085e2022-02-10 18:05:45 +0100589 except Exception as e:
590 skip_df = True
Harald Weltea6c0f882022-07-17 14:23:17 +0200591 df = self._cmd.lchan.selected_file
Harald Welteb2e4b4a2022-07-19 23:48:45 +0200592 df_path = df.fully_qualified_path_str(True)
593 df_skip_reason_str = df_path + \
Harald Weltec91085e2022-02-10 18:05:45 +0100594 "/" + str(f) + ", " + str(e)
595 if context:
596 context['DF_SKIP'] += 1
597 context['DF_SKIP_REASON'].append(df_skip_reason_str)
Philipp Maierf408a402021-04-09 21:16:12 +0200598
Harald Weltec91085e2022-02-10 18:05:45 +0100599 # If the DF was skipped, we never have entered the directory
600 # below, so we must not move up.
601 if skip_df == False:
Philipp Maier7b138b02022-05-31 13:42:56 +0200602 self.walk(indent + 1, action_ef, action_df, context, **kwargs)
Harald Weltea6c0f882022-07-17 14:23:17 +0200603 fcp_dec = self._cmd.lchan.select("..", self._cmd)
Philipp Maierf408a402021-04-09 21:16:12 +0200604
Philipp Maier7b138b02022-05-31 13:42:56 +0200605 elif action_ef:
Harald Weltea6c0f882022-07-17 14:23:17 +0200606 df_before_action = self._cmd.lchan.selected_file
Philipp Maier7b138b02022-05-31 13:42:56 +0200607 action_ef(f, context, **kwargs)
Harald Weltec91085e2022-02-10 18:05:45 +0100608 # When walking through the file system tree the action must not
609 # always restore the currently selected file to the file that
610 # was selected before executing the action() callback.
Harald Weltea6c0f882022-07-17 14:23:17 +0200611 if df_before_action != self._cmd.lchan.selected_file:
Harald Weltec91085e2022-02-10 18:05:45 +0100612 raise RuntimeError("inconsistent walk, %s is currently selected but expecting %s to be selected"
Harald Weltea6c0f882022-07-17 14:23:17 +0200613 % (str(self._cmd.lchan.selected_file), str(df_before_action)))
Philipp Maierff9dae22021-02-25 17:03:21 +0100614
Harald Weltec91085e2022-02-10 18:05:45 +0100615 def do_tree(self, opts):
616 """Display a filesystem-tree with all selectable files"""
617 self.walk()
Philipp Maierff9dae22021-02-25 17:03:21 +0100618
Philipp Maier7b138b02022-05-31 13:42:56 +0200619 def export_ef(self, filename, context, as_json):
620 """ Select and export a single elementary file (EF) """
Harald Weltec91085e2022-02-10 18:05:45 +0100621 context['COUNT'] += 1
Harald Weltea6c0f882022-07-17 14:23:17 +0200622 df = self._cmd.lchan.selected_file
Philipp Maierac34dcc2021-04-01 17:19:05 +0200623
Philipp Maierea81f752022-05-19 10:13:30 +0200624 # The currently selected file (not the file we are going to export)
625 # must always be an ADF or DF. From this starting point we select
626 # the EF we want to export. To maintain consistency we will then
627 # select the current DF again (see comment below).
Harald Weltec91085e2022-02-10 18:05:45 +0100628 if not isinstance(df, CardDF):
629 raise RuntimeError(
630 "currently selected file %s is not a DF or ADF" % str(df))
Philipp Maierac34dcc2021-04-01 17:19:05 +0200631
Harald Weltec91085e2022-02-10 18:05:45 +0100632 df_path_list = df.fully_qualified_path(True)
Harald Welteb2e4b4a2022-07-19 23:48:45 +0200633 df_path = df.fully_qualified_path_str(True)
634 df_path_fid = df.fully_qualified_path_str(False)
Philipp Maier24f7bd32021-02-25 17:06:18 +0100635
Harald Welteb2e4b4a2022-07-19 23:48:45 +0200636 file_str = df_path + "/" + str(filename)
Harald Weltec91085e2022-02-10 18:05:45 +0100637 self._cmd.poutput(boxed_heading_str(file_str))
Philipp Maier24f7bd32021-02-25 17:06:18 +0100638
Harald Welteb2e4b4a2022-07-19 23:48:45 +0200639 self._cmd.poutput("# directory: %s (%s)" % (df_path, df_path_fid))
Harald Weltec91085e2022-02-10 18:05:45 +0100640 try:
Harald Weltea6c0f882022-07-17 14:23:17 +0200641 fcp_dec = self._cmd.lchan.select(filename, self._cmd)
Harald Weltec91085e2022-02-10 18:05:45 +0100642 self._cmd.poutput("# file: %s (%s)" % (
Harald Weltea6c0f882022-07-17 14:23:17 +0200643 self._cmd.lchan.selected_file.name, self._cmd.lchan.selected_file.fid))
Philipp Maier24f7bd32021-02-25 17:06:18 +0100644
Harald Weltea6c0f882022-07-17 14:23:17 +0200645 structure = self._cmd.lchan.selected_file_structure()
Harald Weltec91085e2022-02-10 18:05:45 +0100646 self._cmd.poutput("# structure: %s" % str(structure))
Harald Weltea6c0f882022-07-17 14:23:17 +0200647 self._cmd.poutput("# RAW FCP Template: %s" % str(self._cmd.lchan.selected_file_fcp_hex))
648 self._cmd.poutput("# Decoded FCP Template: %s" % str(self._cmd.lchan.selected_file_fcp))
Philipp Maier24f7bd32021-02-25 17:06:18 +0100649
Harald Weltec91085e2022-02-10 18:05:45 +0100650 for f in df_path_list:
651 self._cmd.poutput("select " + str(f))
Harald Weltea6c0f882022-07-17 14:23:17 +0200652 self._cmd.poutput("select " + self._cmd.lchan.selected_file.name)
Philipp Maier24f7bd32021-02-25 17:06:18 +0100653
Harald Weltec91085e2022-02-10 18:05:45 +0100654 if structure == 'transparent':
Harald Welte08b11ab2022-02-10 18:56:41 +0100655 if as_json:
Harald Weltea6c0f882022-07-17 14:23:17 +0200656 result = self._cmd.lchan.read_binary_dec()
Harald Welte08b11ab2022-02-10 18:56:41 +0100657 self._cmd.poutput("update_binary_decoded '%s'" % json.dumps(result[0], cls=JsonEncoder))
658 else:
Harald Weltea6c0f882022-07-17 14:23:17 +0200659 result = self._cmd.lchan.read_binary()
Harald Welte08b11ab2022-02-10 18:56:41 +0100660 self._cmd.poutput("update_binary " + str(result[0]))
Harald Weltec91085e2022-02-10 18:05:45 +0100661 elif structure == 'cyclic' or structure == 'linear_fixed':
662 # Use number of records specified in select response
Harald Weltea6c0f882022-07-17 14:23:17 +0200663 num_of_rec = self._cmd.lchan.selected_file_num_of_rec()
Harald Welte747a9782022-02-13 17:52:28 +0100664 if num_of_rec:
Harald Weltec91085e2022-02-10 18:05:45 +0100665 for r in range(1, num_of_rec + 1):
Harald Welte08b11ab2022-02-10 18:56:41 +0100666 if as_json:
Harald Weltea6c0f882022-07-17 14:23:17 +0200667 result = self._cmd.lchan.read_record_dec(r)
Harald Welte08b11ab2022-02-10 18:56:41 +0100668 self._cmd.poutput("update_record_decoded %d '%s'" % (r, json.dumps(result[0], cls=JsonEncoder)))
669 else:
Harald Weltea6c0f882022-07-17 14:23:17 +0200670 result = self._cmd.lchan.read_record(r)
Harald Welte08b11ab2022-02-10 18:56:41 +0100671 self._cmd.poutput("update_record %d %s" % (r, str(result[0])))
672
Harald Weltec91085e2022-02-10 18:05:45 +0100673 # When the select response does not return the number of records, read until we hit the
674 # first record that cannot be read.
675 else:
676 r = 1
677 while True:
678 try:
Harald Welte08b11ab2022-02-10 18:56:41 +0100679 if as_json:
Harald Weltea6c0f882022-07-17 14:23:17 +0200680 result = self._cmd.lchan.read_record_dec(r)
Harald Welte08b11ab2022-02-10 18:56:41 +0100681 self._cmd.poutput("update_record_decoded %d '%s'" % (r, json.dumps(result[0], cls=JsonEncoder)))
682 else:
Harald Weltea6c0f882022-07-17 14:23:17 +0200683 result = self._cmd.lchan.read_record(r)
Harald Welte08b11ab2022-02-10 18:56:41 +0100684 self._cmd.poutput("update_record %d %s" % (r, str(result[0])))
Harald Weltec91085e2022-02-10 18:05:45 +0100685 except SwMatchError as e:
686 # We are past the last valid record - stop
687 if e.sw_actual == "9402":
688 break
689 # Some other problem occurred
690 else:
691 raise e
Harald Weltec91085e2022-02-10 18:05:45 +0100692 r = r + 1
693 elif structure == 'ber_tlv':
Harald Weltea6c0f882022-07-17 14:23:17 +0200694 tags = self._cmd.lchan.retrieve_tags()
Harald Weltec91085e2022-02-10 18:05:45 +0100695 for t in tags:
Harald Weltea6c0f882022-07-17 14:23:17 +0200696 result = self._cmd.lchan.retrieve_data(t)
Harald Weltec91085e2022-02-10 18:05:45 +0100697 (tag, l, val, remainer) = bertlv_parse_one(h2b(result[0]))
698 self._cmd.poutput("set_data 0x%02x %s" % (t, b2h(val)))
699 else:
700 raise RuntimeError(
701 'Unsupported structure "%s" of file "%s"' % (structure, filename))
702 except Exception as e:
Harald Welteb2e4b4a2022-07-19 23:48:45 +0200703 bad_file_str = df_path + "/" + str(filename) + ", " + str(e)
Harald Weltec91085e2022-02-10 18:05:45 +0100704 self._cmd.poutput("# bad file: %s" % bad_file_str)
705 context['ERR'] += 1
706 context['BAD'].append(bad_file_str)
Philipp Maier24f7bd32021-02-25 17:06:18 +0100707
Harald Weltec91085e2022-02-10 18:05:45 +0100708 # When reading the file is done, make sure the parent file is
709 # selected again. This will be the usual case, however we need
710 # to check before since we must not select the same DF twice
Harald Weltea6c0f882022-07-17 14:23:17 +0200711 if df != self._cmd.lchan.selected_file:
712 self._cmd.lchan.select(df.fid or df.aid, self._cmd)
Philipp Maierac34dcc2021-04-01 17:19:05 +0200713
Harald Weltec91085e2022-02-10 18:05:45 +0100714 self._cmd.poutput("#")
Philipp Maier24f7bd32021-02-25 17:06:18 +0100715
Harald Weltec91085e2022-02-10 18:05:45 +0100716 export_parser = argparse.ArgumentParser()
717 export_parser.add_argument(
718 '--filename', type=str, default=None, help='only export specific file')
Harald Welte08b11ab2022-02-10 18:56:41 +0100719 export_parser.add_argument(
720 '--json', action='store_true', help='export as JSON (less reliable)')
Philipp Maier24f7bd32021-02-25 17:06:18 +0100721
Harald Weltec91085e2022-02-10 18:05:45 +0100722 @cmd2.with_argparser(export_parser)
723 def do_export(self, opts):
724 """Export files to script that can be imported back later"""
725 context = {'ERR': 0, 'COUNT': 0, 'BAD': [],
726 'DF_SKIP': 0, 'DF_SKIP_REASON': []}
Philipp Maier9a4091d2022-05-19 10:20:30 +0200727 kwargs_export = {'as_json': opts.json}
Philipp Maierf16ac6a2022-05-31 14:08:47 +0200728 exception_str_add = ""
729
Harald Weltec91085e2022-02-10 18:05:45 +0100730 if opts.filename:
Philipp Maier7b138b02022-05-31 13:42:56 +0200731 self.export_ef(opts.filename, context, **kwargs_export)
Harald Weltec91085e2022-02-10 18:05:45 +0100732 else:
Philipp Maierf16ac6a2022-05-31 14:08:47 +0200733 try:
734 self.walk(0, self.export_ef, None, context, **kwargs_export)
735 except Exception as e:
736 print("# Stopping early here due to exception: " + str(e))
737 print("#")
738 exception_str_add = ", also had to stop early due to exception:" + str(e)
Philipp Maier80ce71f2021-04-19 21:24:23 +0200739
Harald Weltec91085e2022-02-10 18:05:45 +0100740 self._cmd.poutput(boxed_heading_str("Export summary"))
Philipp Maier80ce71f2021-04-19 21:24:23 +0200741
Harald Weltec91085e2022-02-10 18:05:45 +0100742 self._cmd.poutput("# total files visited: %u" % context['COUNT'])
743 self._cmd.poutput("# bad files: %u" % context['ERR'])
744 for b in context['BAD']:
745 self._cmd.poutput("# " + b)
Philipp Maierf408a402021-04-09 21:16:12 +0200746
Harald Weltec91085e2022-02-10 18:05:45 +0100747 self._cmd.poutput("# skipped dedicated files(s): %u" %
748 context['DF_SKIP'])
749 for b in context['DF_SKIP_REASON']:
750 self._cmd.poutput("# " + b)
Philipp Maierf408a402021-04-09 21:16:12 +0200751
Harald Weltec91085e2022-02-10 18:05:45 +0100752 if context['ERR'] and context['DF_SKIP']:
Philipp Maierf16ac6a2022-05-31 14:08:47 +0200753 raise RuntimeError("unable to export %i elementary file(s) and %i dedicated file(s)%s" % (
754 context['ERR'], context['DF_SKIP'], exception_str_add))
Harald Weltec91085e2022-02-10 18:05:45 +0100755 elif context['ERR']:
756 raise RuntimeError(
Philipp Maierf16ac6a2022-05-31 14:08:47 +0200757 "unable to export %i elementary file(s)%s" % (context['ERR'], exception_str_add))
Harald Weltec91085e2022-02-10 18:05:45 +0100758 elif context['DF_SKIP']:
759 raise RuntimeError(
Philipp Maierf16ac6a2022-05-31 14:08:47 +0200760 "unable to export %i dedicated files(s)%s" % (context['ERR'], exception_str_add))
Harald Welteb2edd142021-01-08 23:29:35 +0100761
Harald Weltec91085e2022-02-10 18:05:45 +0100762 def do_reset(self, opts):
763 """Reset the Card."""
Philipp Maiere345e112023-06-09 15:19:56 +0200764 atr = self._cmd.card.reset()
765 self._cmd.poutput('Card ATR: %s' % i2h(atr))
Harald Weltec91085e2022-02-10 18:05:45 +0100766 self._cmd.update_prompt()
Harald Weltedaf2b392021-05-03 23:17:29 +0200767
Harald Weltec91085e2022-02-10 18:05:45 +0100768 def do_desc(self, opts):
769 """Display human readable file description for the currently selected file"""
Harald Weltea6c0f882022-07-17 14:23:17 +0200770 desc = self._cmd.lchan.selected_file.desc
Harald Weltec91085e2022-02-10 18:05:45 +0100771 if desc:
772 self._cmd.poutput(desc)
773 else:
774 self._cmd.poutput("no description available")
Philipp Maiera8c9ea92021-09-16 12:51:46 +0200775
Harald Weltec91085e2022-02-10 18:05:45 +0100776 def do_verify_adm(self, arg):
777 """VERIFY the ADM1 PIN"""
778 if arg:
779 # use specified ADM-PIN
780 pin_adm = sanitize_pin_adm(arg)
781 else:
782 # try to find an ADM-PIN if none is specified
783 result = card_key_provider_get_field(
784 'ADM1', key='ICCID', value=self._cmd.iccid)
785 pin_adm = sanitize_pin_adm(result)
786 if pin_adm:
787 self._cmd.poutput(
788 "found ADM-PIN '%s' for ICCID '%s'" % (result, self._cmd.iccid))
789 else:
790 raise ValueError(
791 "cannot find ADM-PIN for ICCID '%s'" % (self._cmd.iccid))
Philipp Maiera8c9ea92021-09-16 12:51:46 +0200792
Harald Weltec91085e2022-02-10 18:05:45 +0100793 if pin_adm:
Harald Welte46255122023-10-21 23:40:42 +0200794 self._cmd.lchan.scc.verify_chv(self._cmd.card._adm_chv_num, h2b(pin_adm))
Harald Weltec91085e2022-02-10 18:05:45 +0100795 else:
796 raise ValueError("error: cannot authenticate, no adm-pin!")
797
Philipp Maier7b9e2442023-03-22 15:19:54 +0100798 def do_cardinfo(self, opts):
799 """Display information about the currently inserted card"""
800 self._cmd.poutput("Card info:")
801 self._cmd.poutput(" Name: %s" % self._cmd.card.name)
Harald Welte46255122023-10-21 23:40:42 +0200802 self._cmd.poutput(" ATR: %s" % b2h(self._cmd.lchan.scc.get_atr()))
Philipp Maier7b9e2442023-03-22 15:19:54 +0100803 self._cmd.poutput(" ICCID: %s" % self._cmd.iccid)
Harald Welte46255122023-10-21 23:40:42 +0200804 self._cmd.poutput(" Class-Byte: %s" % self._cmd.lchan.scc.cla_byte)
805 self._cmd.poutput(" Select-Ctrl: %s" % self._cmd.lchan.scc.sel_ctrl)
Philipp Maier7b9e2442023-03-22 15:19:54 +0100806 self._cmd.poutput(" AIDs:")
807 for a in self._cmd.rs.mf.applications:
808 self._cmd.poutput(" %s" % a)
Harald Welteb2edd142021-01-08 23:29:35 +0100809
Harald Welte31d2cf02021-04-03 10:47:29 +0200810@with_default_category('ISO7816 Commands')
811class Iso7816Commands(CommandSet):
Harald Weltec91085e2022-02-10 18:05:45 +0100812 def __init__(self):
813 super().__init__()
Harald Welte31d2cf02021-04-03 10:47:29 +0200814
Harald Weltec91085e2022-02-10 18:05:45 +0100815 def do_select(self, opts):
816 """SELECT a File (ADF/DF/EF)"""
817 if len(opts.arg_list) == 0:
Harald Welteb2e4b4a2022-07-19 23:48:45 +0200818 path = self._cmd.lchan.selected_file.fully_qualified_path_str(True)
819 path_fid = self._cmd.lchan.selected_file.fully_qualified_path_str(False)
820 self._cmd.poutput("currently selected file: %s (%s)" % (path, path_fid))
Harald Weltec91085e2022-02-10 18:05:45 +0100821 return
Harald Welte31d2cf02021-04-03 10:47:29 +0200822
Harald Weltec91085e2022-02-10 18:05:45 +0100823 path = opts.arg_list[0]
Harald Weltea6c0f882022-07-17 14:23:17 +0200824 fcp_dec = self._cmd.lchan.select(path, self._cmd)
Harald Weltec91085e2022-02-10 18:05:45 +0100825 self._cmd.update_prompt()
826 self._cmd.poutput_json(fcp_dec)
Harald Welte31d2cf02021-04-03 10:47:29 +0200827
Harald Weltec91085e2022-02-10 18:05:45 +0100828 def complete_select(self, text, line, begidx, endidx) -> List[str]:
829 """Command Line tab completion for SELECT"""
Harald Weltea6c0f882022-07-17 14:23:17 +0200830 index_dict = {1: self._cmd.lchan.selected_file.get_selectable_names()}
Harald Weltec91085e2022-02-10 18:05:45 +0100831 return self._cmd.index_based_complete(text, line, begidx, endidx, index_dict=index_dict)
Harald Welte31d2cf02021-04-03 10:47:29 +0200832
Harald Weltec91085e2022-02-10 18:05:45 +0100833 def get_code(self, code):
834 """Use code either directly or try to get it from external data source"""
835 auto = ('PIN1', 'PIN2', 'PUK1', 'PUK2')
Harald Welte31d2cf02021-04-03 10:47:29 +0200836
Harald Weltec91085e2022-02-10 18:05:45 +0100837 if str(code).upper() not in auto:
838 return sanitize_pin_adm(code)
Harald Welte31d2cf02021-04-03 10:47:29 +0200839
Harald Weltec91085e2022-02-10 18:05:45 +0100840 result = card_key_provider_get_field(
841 str(code), key='ICCID', value=self._cmd.iccid)
842 result = sanitize_pin_adm(result)
843 if result:
844 self._cmd.poutput("found %s '%s' for ICCID '%s'" %
845 (code.upper(), result, self._cmd.iccid))
846 else:
847 self._cmd.poutput("cannot find %s for ICCID '%s'" %
848 (code.upper(), self._cmd.iccid))
849 return result
Harald Welte31d2cf02021-04-03 10:47:29 +0200850
Harald Weltec91085e2022-02-10 18:05:45 +0100851 verify_chv_parser = argparse.ArgumentParser()
852 verify_chv_parser.add_argument(
853 '--pin-nr', type=int, default=1, help='PIN Number, 1=PIN1, 2=PIN2 or custom value (decimal)')
854 verify_chv_parser.add_argument(
855 'pin_code', type=str, help='PIN code digits, \"PIN1\" or \"PIN2\" to get PIN code from external data source')
Harald Welte31d2cf02021-04-03 10:47:29 +0200856
Harald Weltec91085e2022-02-10 18:05:45 +0100857 @cmd2.with_argparser(verify_chv_parser)
858 def do_verify_chv(self, opts):
Harald Welte12af7932022-02-15 16:39:08 +0100859 """Verify (authenticate) using specified CHV (PIN) code, which is how the specifications
860 call it if you authenticate yourself using the specified PIN. There usually is at least PIN1 and
861 PIN2."""
Harald Weltec91085e2022-02-10 18:05:45 +0100862 pin = self.get_code(opts.pin_code)
Harald Welte46255122023-10-21 23:40:42 +0200863 (data, sw) = self._cmd.lchan.scc.verify_chv(opts.pin_nr, h2b(pin))
Harald Weltec91085e2022-02-10 18:05:45 +0100864 self._cmd.poutput("CHV verification successful")
Harald Welte31d2cf02021-04-03 10:47:29 +0200865
Harald Weltec91085e2022-02-10 18:05:45 +0100866 unblock_chv_parser = argparse.ArgumentParser()
867 unblock_chv_parser.add_argument(
868 '--pin-nr', type=int, default=1, help='PUK Number, 1=PIN1, 2=PIN2 or custom value (decimal)')
869 unblock_chv_parser.add_argument(
870 'puk_code', type=str, help='PUK code digits \"PUK1\" or \"PUK2\" to get PUK code from external data source')
871 unblock_chv_parser.add_argument(
872 'new_pin_code', type=str, help='PIN code digits \"PIN1\" or \"PIN2\" to get PIN code from external data source')
Harald Welte31d2cf02021-04-03 10:47:29 +0200873
Harald Weltec91085e2022-02-10 18:05:45 +0100874 @cmd2.with_argparser(unblock_chv_parser)
875 def do_unblock_chv(self, opts):
876 """Unblock PIN code using specified PUK code"""
877 new_pin = self.get_code(opts.new_pin_code)
878 puk = self.get_code(opts.puk_code)
Harald Welte46255122023-10-21 23:40:42 +0200879 (data, sw) = self._cmd.lchan.scc.unblock_chv(
Harald Weltec91085e2022-02-10 18:05:45 +0100880 opts.pin_nr, h2b(puk), h2b(new_pin))
881 self._cmd.poutput("CHV unblock successful")
Harald Welte31d2cf02021-04-03 10:47:29 +0200882
Harald Weltec91085e2022-02-10 18:05:45 +0100883 change_chv_parser = argparse.ArgumentParser()
884 change_chv_parser.add_argument(
885 '--pin-nr', type=int, default=1, help='PUK Number, 1=PIN1, 2=PIN2 or custom value (decimal)')
886 change_chv_parser.add_argument(
887 'pin_code', type=str, help='PIN code digits \"PIN1\" or \"PIN2\" to get PIN code from external data source')
888 change_chv_parser.add_argument(
889 'new_pin_code', type=str, help='PIN code digits \"PIN1\" or \"PIN2\" to get PIN code from external data source')
Harald Welte31d2cf02021-04-03 10:47:29 +0200890
Harald Weltec91085e2022-02-10 18:05:45 +0100891 @cmd2.with_argparser(change_chv_parser)
892 def do_change_chv(self, opts):
893 """Change PIN code to a new PIN code"""
894 new_pin = self.get_code(opts.new_pin_code)
895 pin = self.get_code(opts.pin_code)
Harald Welte46255122023-10-21 23:40:42 +0200896 (data, sw) = self._cmd.lchan.scc.change_chv(
Harald Weltec91085e2022-02-10 18:05:45 +0100897 opts.pin_nr, h2b(pin), h2b(new_pin))
898 self._cmd.poutput("CHV change successful")
Harald Welte31d2cf02021-04-03 10:47:29 +0200899
Harald Weltec91085e2022-02-10 18:05:45 +0100900 disable_chv_parser = argparse.ArgumentParser()
901 disable_chv_parser.add_argument(
902 '--pin-nr', type=int, default=1, help='PIN Number, 1=PIN1, 2=PIN2 or custom value (decimal)')
903 disable_chv_parser.add_argument(
904 'pin_code', type=str, help='PIN code digits, \"PIN1\" or \"PIN2\" to get PIN code from external data source')
Harald Welte31d2cf02021-04-03 10:47:29 +0200905
Harald Weltec91085e2022-02-10 18:05:45 +0100906 @cmd2.with_argparser(disable_chv_parser)
907 def do_disable_chv(self, opts):
908 """Disable PIN code using specified PIN code"""
909 pin = self.get_code(opts.pin_code)
Harald Welte46255122023-10-21 23:40:42 +0200910 (data, sw) = self._cmd.lchan.scc.disable_chv(opts.pin_nr, h2b(pin))
Harald Weltec91085e2022-02-10 18:05:45 +0100911 self._cmd.poutput("CHV disable successful")
Harald Welte31d2cf02021-04-03 10:47:29 +0200912
Harald Weltec91085e2022-02-10 18:05:45 +0100913 enable_chv_parser = argparse.ArgumentParser()
914 enable_chv_parser.add_argument(
915 '--pin-nr', type=int, default=1, help='PIN Number, 1=PIN1, 2=PIN2 or custom value (decimal)')
916 enable_chv_parser.add_argument(
917 'pin_code', type=str, help='PIN code digits, \"PIN1\" or \"PIN2\" to get PIN code from external data source')
Harald Welte31d2cf02021-04-03 10:47:29 +0200918
Harald Weltec91085e2022-02-10 18:05:45 +0100919 @cmd2.with_argparser(enable_chv_parser)
920 def do_enable_chv(self, opts):
921 """Enable PIN code using specified PIN code"""
922 pin = self.get_code(opts.pin_code)
Harald Welte46255122023-10-21 23:40:42 +0200923 (data, sw) = self._cmd.lchan.scc.enable_chv(opts.pin_nr, h2b(pin))
Harald Weltec91085e2022-02-10 18:05:45 +0100924 self._cmd.poutput("CHV enable successful")
Harald Welte31d2cf02021-04-03 10:47:29 +0200925
Harald Weltec91085e2022-02-10 18:05:45 +0100926 def do_deactivate_file(self, opts):
Harald Welte799c3542022-02-15 15:56:28 +0100927 """Deactivate the currently selected EF"""
Harald Welte46255122023-10-21 23:40:42 +0200928 (data, sw) = self._cmd.lchan.scc.deactivate_file()
Harald Weltea4631612021-04-10 18:17:55 +0200929
Harald Welte799c3542022-02-15 15:56:28 +0100930 activate_file_parser = argparse.ArgumentParser()
931 activate_file_parser.add_argument('NAME', type=str, help='File name or FID of file to activate')
932 @cmd2.with_argparser(activate_file_parser)
Harald Weltec91085e2022-02-10 18:05:45 +0100933 def do_activate_file(self, opts):
Harald Welte12af7932022-02-15 16:39:08 +0100934 """Activate the specified EF. This used to be called REHABILITATE in TS 11.11 for classic
935 SIM. You need to specify the name or FID of the file to activate."""
Harald Weltea6c0f882022-07-17 14:23:17 +0200936 (data, sw) = self._cmd.lchan.activate_file(opts.NAME)
Harald Welte485692b2021-05-25 22:21:44 +0200937
Harald Weltec91085e2022-02-10 18:05:45 +0100938 def complete_activate_file(self, text, line, begidx, endidx) -> List[str]:
939 """Command Line tab completion for ACTIVATE FILE"""
Harald Weltea6c0f882022-07-17 14:23:17 +0200940 index_dict = {1: self._cmd.lchan.selected_file.get_selectable_names()}
Harald Weltec91085e2022-02-10 18:05:45 +0100941 return self._cmd.index_based_complete(text, line, begidx, endidx, index_dict=index_dict)
Harald Welte31d2cf02021-04-03 10:47:29 +0200942
Harald Weltec91085e2022-02-10 18:05:45 +0100943 open_chan_parser = argparse.ArgumentParser()
944 open_chan_parser.add_argument(
945 'chan_nr', type=int, default=0, help='Channel Number')
Harald Welte703f9332021-04-10 18:39:32 +0200946
Harald Weltec91085e2022-02-10 18:05:45 +0100947 @cmd2.with_argparser(open_chan_parser)
948 def do_open_channel(self, opts):
949 """Open a logical channel."""
Harald Welte46255122023-10-21 23:40:42 +0200950 (data, sw) = self._cmd.lchan.scc.manage_channel(
Harald Weltec91085e2022-02-10 18:05:45 +0100951 mode='open', lchan_nr=opts.chan_nr)
Harald Weltebdf59572023-10-21 20:06:19 +0200952 # this is executed only in successful case, as unsuccessful raises exception
953 self._cmd.lchan.add_lchan(opts.chan_nr)
Harald Welte703f9332021-04-10 18:39:32 +0200954
Harald Weltec91085e2022-02-10 18:05:45 +0100955 close_chan_parser = argparse.ArgumentParser()
956 close_chan_parser.add_argument(
957 'chan_nr', type=int, default=0, help='Channel Number')
Harald Welte703f9332021-04-10 18:39:32 +0200958
Harald Weltec91085e2022-02-10 18:05:45 +0100959 @cmd2.with_argparser(close_chan_parser)
960 def do_close_channel(self, opts):
961 """Close a logical channel."""
Harald Welte46255122023-10-21 23:40:42 +0200962 (data, sw) = self._cmd.lchan.scc.manage_channel(
Harald Weltec91085e2022-02-10 18:05:45 +0100963 mode='close', lchan_nr=opts.chan_nr)
Harald Weltebdf59572023-10-21 20:06:19 +0200964 # this is executed only in successful case, as unsuccessful raises exception
965 self._cmd.rs.del_lchan(opts.chan_nr)
Harald Welte703f9332021-04-10 18:39:32 +0200966
Harald Welte20650992023-10-21 23:47:02 +0200967 switch_chan_parser = argparse.ArgumentParser()
968 switch_chan_parser.add_argument(
969 'chan_nr', type=int, default=0, help='Channel Number')
970
971 @cmd2.with_argparser(switch_chan_parser)
972 def do_switch_channel(self, opts):
973 """Switch currently active logical channel."""
974 self._cmd.lchan._select_pre(self._cmd)
975 self._cmd.lchan = self._cmd.rs.lchan[opts.chan_nr]
976 self._cmd.lchan._select_post(self._cmd)
977 self._cmd.update_prompt()
978
Harald Weltec91085e2022-02-10 18:05:45 +0100979 def do_status(self, opts):
980 """Perform the STATUS command."""
Harald Weltea6c0f882022-07-17 14:23:17 +0200981 fcp_dec = self._cmd.lchan.status()
Harald Weltec91085e2022-02-10 18:05:45 +0100982 self._cmd.poutput_json(fcp_dec)
Harald Welte34b05d32021-05-25 22:03:13 +0200983
Harald Welteec950532021-10-20 13:09:00 +0200984
Harald Weltecab26c72022-08-06 16:12:30 +0200985class Proact(ProactiveHandler):
986 def receive_fetch(self, pcmd: ProactiveCommand):
987 # print its parsed representation
988 print(pcmd.decoded)
989 # TODO: implement the basics, such as SMS Sending, ...
990
991
Harald Welte703f9332021-04-10 18:39:32 +0200992
Harald Weltef422eb12023-06-09 11:15:09 +0200993option_parser = argparse.ArgumentParser(description='interactive SIM card shell',
Harald Weltef2e761c2021-04-11 11:56:44 +0200994 formatter_class=argparse.ArgumentDefaultsHelpFormatter)
Harald Welte28c24312021-04-11 12:19:36 +0200995argparse_add_reader_args(option_parser)
Harald Weltec8ff0262021-04-11 12:06:13 +0200996
997global_group = option_parser.add_argument_group('General Options')
998global_group.add_argument('--script', metavar='PATH', default=None,
Harald Welte28c24312021-04-11 12:19:36 +0200999 help='script with pySim-shell commands to be executed automatically at start-up')
Harald Weltec91085e2022-02-10 18:05:45 +01001000global_group.add_argument('--csv', metavar='FILE',
1001 default=None, help='Read card data from CSV file')
Philipp Maier76667642021-09-22 16:53:22 +02001002global_group.add_argument("--card_handler", dest="card_handler_config", metavar="FILE",
Harald Weltec91085e2022-02-10 18:05:45 +01001003 help="Use automatic card handling machine")
Harald Weltec8ff0262021-04-11 12:06:13 +02001004
1005adm_group = global_group.add_mutually_exclusive_group()
1006adm_group.add_argument('-a', '--pin-adm', metavar='PIN_ADM1', dest='pin_adm', default=None,
1007 help='ADM PIN used for provisioning (overwrites default)')
1008adm_group.add_argument('-A', '--pin-adm-hex', metavar='PIN_ADM1_HEX', dest='pin_adm_hex', default=None,
1009 help='ADM PIN used for provisioning, as hex string (16 characters long)')
Harald Welteb2edd142021-01-08 23:29:35 +01001010
Harald Welte38306df2023-07-11 21:17:55 +02001011option_parser.add_argument("command", nargs='?',
1012 help="A pySim-shell command that would optionally be executed at startup")
1013option_parser.add_argument('command_args', nargs=argparse.REMAINDER,
1014 help="Optional Arguments for command")
1015
Harald Welteb2edd142021-01-08 23:29:35 +01001016
1017if __name__ == '__main__':
1018
Harald Weltec91085e2022-02-10 18:05:45 +01001019 # Parse options
1020 opts = option_parser.parse_args()
Harald Welteb2edd142021-01-08 23:29:35 +01001021
Harald Weltec91085e2022-02-10 18:05:45 +01001022 # If a script file is specified, be sure that it actually exists
1023 if opts.script:
1024 if not os.access(opts.script, os.R_OK):
1025 print("Invalid script file!")
1026 sys.exit(2)
Philipp Maier13e258d2021-04-08 17:48:49 +02001027
Harald Weltec91085e2022-02-10 18:05:45 +01001028 # Register csv-file as card data provider, either from specified CSV
1029 # or from CSV file in home directory
1030 csv_default = str(Path.home()) + "/.osmocom/pysim/card_data.csv"
1031 if opts.csv:
1032 card_key_provider_register(CardKeyProviderCsv(opts.csv))
1033 if os.path.isfile(csv_default):
1034 card_key_provider_register(CardKeyProviderCsv(csv_default))
Philipp Maier2b11c322021-03-17 12:37:39 +01001035
Harald Weltec91085e2022-02-10 18:05:45 +01001036 # Init card reader driver
Harald Weltecab26c72022-08-06 16:12:30 +02001037 sl = init_reader(opts, proactive_handler = Proact())
Philipp Maierea95c392021-09-16 13:10:19 +02001038
Harald Weltec91085e2022-02-10 18:05:45 +01001039 # Create a card handler (for bulk provisioning)
1040 if opts.card_handler_config:
1041 ch = CardHandlerAuto(None, opts.card_handler_config)
1042 else:
1043 ch = CardHandler(sl)
Philipp Maier76667642021-09-22 16:53:22 +02001044
Harald Weltec91085e2022-02-10 18:05:45 +01001045 # Detect and initialize the card in the reader. This may fail when there
1046 # is no card in the reader or the card is unresponsive. PysimApp is
1047 # able to tolerate and recover from that.
1048 try:
1049 rs, card = init_card(sl)
1050 app = PysimApp(card, rs, sl, ch, opts.script)
1051 except:
Philipp Maier6bfa8a82023-10-09 13:32:49 +02001052 print("Card initialization (%s) failed with an exception:" % str(sl))
Harald Weltec91085e2022-02-10 18:05:45 +01001053 print("---------------------8<---------------------")
1054 traceback.print_exc()
1055 print("---------------------8<---------------------")
1056 print("(you may still try to recover from this manually by using the 'equip' command.)")
1057 print(
1058 " it should also be noted that some readers may behave strangely when no card")
1059 print(" is inserted.)")
1060 print("")
Philipp Maier8e03f2f2023-10-09 13:27:59 +02001061 app = PysimApp(None, None, sl, ch, opts.script)
Philipp Maierea95c392021-09-16 13:10:19 +02001062
Harald Weltec91085e2022-02-10 18:05:45 +01001063 # If the user supplies an ADM PIN at via commandline args authenticate
1064 # immediately so that the user does not have to use the shell commands
1065 pin_adm = sanitize_pin_adm(opts.pin_adm, opts.pin_adm_hex)
1066 if pin_adm:
1067 if not card:
1068 print("Card error, cannot do ADM verification with supplied ADM pin now.")
1069 try:
Philipp Maier4840d4d2023-09-06 14:54:14 +02001070 card._scc.verify_chv(card._adm_chv_num, h2b(pin_adm))
Harald Weltec91085e2022-02-10 18:05:45 +01001071 except Exception as e:
1072 print(e)
Philipp Maier228c98e2021-03-10 20:14:06 +01001073
Harald Welte38306df2023-07-11 21:17:55 +02001074 if opts.command:
1075 app.onecmd_plus_hooks('{} {}'.format(opts.command, ' '.join(opts.command_args)))
1076 else:
1077 app.cmdloop()