blob: 022b276ad5b81f99753cdf3bd5c9e4288870c0a5 [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
80
Harald Welte4442b3d2021-04-03 09:00:16 +020081from pySim.card_key_provider import CardKeyProviderCsv, card_key_provider_register, card_key_provider_get_field
Philipp Maier2b11c322021-03-17 12:37:39 +010082
Harald Weltec91085e2022-02-10 18:05:45 +010083
Philipp Maierea95c392021-09-16 13:10:19 +020084def init_card(sl):
Harald Weltec91085e2022-02-10 18:05:45 +010085 """
86 Detect card in reader and setup card profile and runtime state. This
87 function must be called at least once on startup. The card and runtime
88 state object (rs) is required for all pySim-shell commands.
89 """
Philipp Maierea95c392021-09-16 13:10:19 +020090
Philipp Maier91c971b2023-10-09 10:48:46 +020091 # Create command layer
92 scc = SimCardCommands(transport=sl)
93
Harald Weltec91085e2022-02-10 18:05:45 +010094 # Wait up to three seconds for a card in reader and try to detect
95 # the card type.
96 print("Waiting for card...")
97 try:
98 sl.wait_for_card(3)
99 except NoCardError:
100 print("No card detected!")
101 return None, None
102 except:
103 print("Card not readable!")
104 return None, None
Philipp Maierea95c392021-09-16 13:10:19 +0200105
Philipp Maier24031252022-06-14 16:16:42 +0200106 generic_card = False
Harald Weltef8d2e2b2023-07-09 17:58:38 +0200107 card = card_detect(scc)
Harald Weltec91085e2022-02-10 18:05:45 +0100108 if card is None:
109 print("Warning: Could not detect card type - assuming a generic card type...")
Harald Weltef8d2e2b2023-07-09 17:58:38 +0200110 card = SimCardBase(scc)
Philipp Maier24031252022-06-14 16:16:42 +0200111 generic_card = True
Philipp Maiera028c7d2021-11-08 16:12:03 +0100112
Harald Weltec91085e2022-02-10 18:05:45 +0100113 profile = CardProfile.pick(scc)
114 if profile is None:
115 print("Unsupported card type!")
Philipp Maier7226c092022-06-01 17:58:38 +0200116 return None, card
Philipp Maierea95c392021-09-16 13:10:19 +0200117
Philipp Maier24031252022-06-14 16:16:42 +0200118 # ETSI TS 102 221, Table 9.3 specifies a default for the PIN key
119 # references, however card manufactures may still decide to pick an
120 # arbitrary key reference. In case we run on a generic card class that is
121 # detected as an UICC, we will pick the key reference that is officially
122 # specified.
123 if generic_card and isinstance(profile, CardProfileUICC):
124 card._adm_chv_num = 0x0A
125
Harald Weltec91085e2022-02-10 18:05:45 +0100126 print("Info: Card is of type: %s" % str(profile))
Philipp Maiera028c7d2021-11-08 16:12:03 +0100127
Harald Welte6ad9a242023-07-11 18:55:29 +0200128 # FIXME: this shouldn't really be here but somewhere else/more generic.
129 # We cannot do it within pySim/profile.py as that would create circular
130 # dependencies between the individual profiles and profile.py.
Harald Weltec91085e2022-02-10 18:05:45 +0100131 if isinstance(profile, CardProfileUICC):
Harald Welte6ad9a242023-07-11 18:55:29 +0200132 for app_cls in CardApplication.__subclasses__():
133 constr_sig = inspect.signature(app_cls.__init__)
134 # skip any intermediary sub-classes such as CardApplicationSD
135 if len(constr_sig.parameters) != 1:
136 continue
137 profile.add_application(app_cls())
Harald Weltef4a01472023-07-04 21:14:23 +0200138 # We have chosen SimCard() above, but we now know it actually is an UICC
139 # so it's safe to assume it supports USIM application (which we're adding above).
140 # IF we don't do this, we will have a SimCard but try USIM specific commands like
141 # the update_ust method (see https://osmocom.org/issues/6055)
142 if generic_card:
Harald Weltef8d2e2b2023-07-09 17:58:38 +0200143 card = UiccCardBase(scc)
Philipp Maiera028c7d2021-11-08 16:12:03 +0100144
Harald Weltec91085e2022-02-10 18:05:45 +0100145 # Create runtime state with card profile
146 rs = RuntimeState(card, profile)
Philipp Maierea95c392021-09-16 13:10:19 +0200147
Harald Weltec91085e2022-02-10 18:05:45 +0100148 CardModel.apply_matching_models(scc, rs)
Harald Weltef44256c2021-10-14 15:53:39 +0200149
Harald Weltec91085e2022-02-10 18:05:45 +0100150 # inform the transport that we can do context-specific SW interpretation
151 sl.set_sw_interpreter(rs)
Philipp Maierea95c392021-09-16 13:10:19 +0200152
Harald Weltec91085e2022-02-10 18:05:45 +0100153 return rs, card
154
Philipp Maier2b11c322021-03-17 12:37:39 +0100155
Harald Weltee1268722023-06-24 07:57:08 +0200156class Cmd2Compat(cmd2.Cmd):
157 """Backwards-compatibility wrapper around cmd2.Cmd to support older and newer
158 releases. See https://github.com/python-cmd2/cmd2/blob/master/CHANGELOG.md"""
159 def run_editor(self, file_path: Optional[str] = None) -> None:
160 if version.parse(cmd2.__version__) < version.parse("2.0.0"):
Harald Welte0ec01502023-06-24 10:00:12 +0200161 return self._run_editor(file_path) # pylint: disable=no-member
Harald Weltee1268722023-06-24 07:57:08 +0200162 else:
Harald Welte0ec01502023-06-24 10:00:12 +0200163 return super().run_editor(file_path) # pylint: disable=no-member
164
165class Settable2Compat(cmd2.Settable):
166 """Backwards-compatibility wrapper around cmd2.Settable to support older and newer
167 releases. See https://github.com/python-cmd2/cmd2/blob/master/CHANGELOG.md"""
168 def __init__(self, name, val_type, description, settable_object, **kwargs):
169 if version.parse(cmd2.__version__) < version.parse("2.0.0"):
170 super().__init__(name, val_type, description, **kwargs) # pylint: disable=no-value-for-parameter
171 else:
172 super().__init__(name, val_type, description, settable_object, **kwargs) # pylint: disable=too-many-function-args
Harald Weltee1268722023-06-24 07:57:08 +0200173
174class PysimApp(Cmd2Compat):
Harald Weltec91085e2022-02-10 18:05:45 +0100175 CUSTOM_CATEGORY = 'pySim Commands'
Philipp Maier76667642021-09-22 16:53:22 +0200176
Harald Weltec91085e2022-02-10 18:05:45 +0100177 def __init__(self, card, rs, sl, ch, script=None):
Harald Weltec85d4062023-04-27 17:10:17 +0200178 if version.parse(cmd2.__version__) < version.parse("2.0.0"):
179 kwargs = {'use_ipython': True}
180 else:
181 kwargs = {'include_ipy': True}
182
Oliver Smithe47ea5f2023-05-23 12:54:15 +0200183 # pylint: disable=unexpected-keyword-arg
Harald Weltec91085e2022-02-10 18:05:45 +0100184 super().__init__(persistent_history_file='~/.pysim_shell_history', allow_cli_args=False,
Harald Weltec85d4062023-04-27 17:10:17 +0200185 auto_load_commands=False, startup_script=script, **kwargs)
Harald Welte961b8032023-04-27 17:30:22 +0200186 self.intro = style('Welcome to pySim-shell!', fg=RED)
Harald Weltec91085e2022-02-10 18:05:45 +0100187 self.default_category = 'pySim-shell built-in commands'
188 self.card = None
189 self.rs = None
Harald Weltea6c0f882022-07-17 14:23:17 +0200190 self.lchan = None
191 self.py_locals = {'card': self.card, 'rs': self.rs, 'lchan': self.lchan}
Harald Weltec91085e2022-02-10 18:05:45 +0100192 self.sl = sl
193 self.ch = ch
Harald Welte1748b932021-04-06 21:12:25 +0200194
Harald Weltec91085e2022-02-10 18:05:45 +0100195 self.numeric_path = False
Harald Weltec91085e2022-02-10 18:05:45 +0100196 self.conserve_write = True
Harald Weltec91085e2022-02-10 18:05:45 +0100197 self.json_pretty_print = True
Harald Weltec91085e2022-02-10 18:05:45 +0100198 self.apdu_trace = False
Philipp Maier5d698e52021-09-16 13:18:01 +0200199
Harald Welte0ec01502023-06-24 10:00:12 +0200200 self.add_settable(Settable2Compat('numeric_path', bool, 'Print File IDs instead of names', self,
201 onchange_cb=self._onchange_numeric_path))
202 self.add_settable(Settable2Compat('conserve_write', bool, 'Read and compare before write', self,
203 onchange_cb=self._onchange_conserve_write))
204 self.add_settable(Settable2Compat('json_pretty_print', bool, 'Pretty-Print JSON output', self))
205 self.add_settable(Settable2Compat('apdu_trace', bool, 'Trace and display APDUs exchanged with card', self,
206 onchange_cb=self._onchange_apdu_trace))
Harald Weltec91085e2022-02-10 18:05:45 +0100207 self.equip(card, rs)
Philipp Maier5d698e52021-09-16 13:18:01 +0200208
Harald Weltec91085e2022-02-10 18:05:45 +0100209 def equip(self, card, rs):
210 """
211 Equip pySim-shell with the supplied card and runtime state, add (or remove) all required settables and
212 and commands to enable card operations.
213 """
Philipp Maier76667642021-09-22 16:53:22 +0200214
Harald Weltec91085e2022-02-10 18:05:45 +0100215 rc = False
Philipp Maier5d698e52021-09-16 13:18:01 +0200216
Harald Weltec91085e2022-02-10 18:05:45 +0100217 # Unequip everything from pySim-shell that would not work in unequipped state
218 if self.rs:
Harald Weltea6c0f882022-07-17 14:23:17 +0200219 lchan = self.rs.lchan[0]
220 lchan.unregister_cmds(self)
iw0f818acd2023-06-19 10:27:04 +0200221 if self.rs.profile:
222 for cmd_set in self.rs.profile.shell_cmdsets:
223 self.unregister_command_set(cmd_set)
224
Harald Welte892526f2023-06-06 17:09:50 +0200225 for cmds in [Iso7816Commands, Ts102222Commands, PySimCommands]:
Harald Weltec91085e2022-02-10 18:05:45 +0100226 cmd_set = self.find_commandsets(cmds)
227 if cmd_set:
228 self.unregister_command_set(cmd_set[0])
Philipp Maier5d698e52021-09-16 13:18:01 +0200229
Harald Weltec91085e2022-02-10 18:05:45 +0100230 self.card = card
231 self.rs = rs
Philipp Maier5d698e52021-09-16 13:18:01 +0200232
Harald Weltec91085e2022-02-10 18:05:45 +0100233 # When a card object and a runtime state is present, (re)equip pySim-shell with everything that is
234 # needed to operate on cards.
235 if self.card and self.rs:
Harald Weltea6c0f882022-07-17 14:23:17 +0200236 self.lchan = self.rs.lchan[0]
Harald Weltec91085e2022-02-10 18:05:45 +0100237 self._onchange_conserve_write(
238 'conserve_write', False, self.conserve_write)
239 self._onchange_apdu_trace('apdu_trace', False, self.apdu_trace)
Harald Welte659781c2023-06-06 17:00:51 +0200240 if self.rs.profile:
241 for cmd_set in self.rs.profile.shell_cmdsets:
242 self.register_command_set(cmd_set)
Harald Weltec91085e2022-02-10 18:05:45 +0100243 self.register_command_set(Iso7816Commands())
Harald Welte3c9b7842021-10-19 21:44:24 +0200244 self.register_command_set(Ts102222Commands())
Harald Weltec91085e2022-02-10 18:05:45 +0100245 self.register_command_set(PySimCommands())
Philipp Maiera42ee6f2023-08-16 10:44:57 +0200246
Philipp Maier7c0cd0a2023-10-16 15:02:07 +0200247 try:
248 self.lchan.select('MF/EF.ICCID', self)
249 self.iccid = dec_iccid(self.lchan.read_binary()[0])
250 except:
251 self.iccid = None
Philipp Maiera42ee6f2023-08-16 10:44:57 +0200252
Harald Weltea6c0f882022-07-17 14:23:17 +0200253 self.lchan.select('MF', self)
Harald Weltec91085e2022-02-10 18:05:45 +0100254 rc = True
255 else:
256 self.poutput("pySim-shell not equipped!")
Philipp Maier5d698e52021-09-16 13:18:01 +0200257
Harald Weltec91085e2022-02-10 18:05:45 +0100258 self.update_prompt()
259 return rc
Harald Welteb2edd142021-01-08 23:29:35 +0100260
Harald Weltec91085e2022-02-10 18:05:45 +0100261 def poutput_json(self, data, force_no_pretty=False):
262 """like cmd2.poutput() but for a JSON serializable dict."""
263 if force_no_pretty or self.json_pretty_print == False:
264 output = json.dumps(data, cls=JsonEncoder)
265 else:
266 output = json.dumps(data, cls=JsonEncoder, indent=4)
267 self.poutput(output)
Harald Welteb2edd142021-01-08 23:29:35 +0100268
Harald Weltec91085e2022-02-10 18:05:45 +0100269 def _onchange_numeric_path(self, param_name, old, new):
270 self.update_prompt()
Philipp Maier38c74f62021-03-17 17:19:52 +0100271
Harald Weltec91085e2022-02-10 18:05:45 +0100272 def _onchange_conserve_write(self, param_name, old, new):
273 if self.rs:
274 self.rs.conserve_write = new
Harald Welte7829d8a2021-04-10 11:28:53 +0200275
Harald Weltec91085e2022-02-10 18:05:45 +0100276 def _onchange_apdu_trace(self, param_name, old, new):
277 if self.card:
278 if new == True:
279 self.card._scc._tp.apdu_tracer = self.Cmd2ApduTracer(self)
280 else:
281 self.card._scc._tp.apdu_tracer = None
Harald Welte7829d8a2021-04-10 11:28:53 +0200282
Harald Weltec91085e2022-02-10 18:05:45 +0100283 class Cmd2ApduTracer(ApduTracer):
284 def __init__(self, cmd2_app):
Philipp Maier91c971b2023-10-09 10:48:46 +0200285 self.cmd2 = cmd2_app
Harald Welte7829d8a2021-04-10 11:28:53 +0200286
Harald Weltec91085e2022-02-10 18:05:45 +0100287 def trace_response(self, cmd, sw, resp):
288 self.cmd2.poutput("-> %s %s" % (cmd[:10], cmd[10:]))
289 self.cmd2.poutput("<- %s: %s" % (sw, resp))
Harald Welteb2edd142021-01-08 23:29:35 +0100290
Harald Weltec91085e2022-02-10 18:05:45 +0100291 def update_prompt(self):
Harald Weltea6c0f882022-07-17 14:23:17 +0200292 if self.lchan:
Harald Welteb2e4b4a2022-07-19 23:48:45 +0200293 path_str = self.lchan.selected_file.fully_qualified_path_str(not self.numeric_path)
Harald Welte237ddb52023-10-22 10:36:58 +0200294 self.prompt = 'pySIM-shell (%02u:%s)> ' % (self.lchan.lchan_nr, path_str)
Harald Weltec91085e2022-02-10 18:05:45 +0100295 else:
Philipp Maier7226c092022-06-01 17:58:38 +0200296 if self.card:
297 self.prompt = 'pySIM-shell (no card profile)> '
298 else:
299 self.prompt = 'pySIM-shell (no card)> '
Harald Welteb2edd142021-01-08 23:29:35 +0100300
Harald Weltec91085e2022-02-10 18:05:45 +0100301 @cmd2.with_category(CUSTOM_CATEGORY)
302 def do_intro(self, _):
303 """Display the intro banner"""
304 self.poutput(self.intro)
Philipp Maier9764de22021-11-03 10:44:39 +0100305
Harald Weltec91085e2022-02-10 18:05:45 +0100306 def do_eof(self, _: argparse.Namespace) -> bool:
307 self.poutput("")
308 return self.do_quit('')
Philipp Maier5d698e52021-09-16 13:18:01 +0200309
Harald Weltec91085e2022-02-10 18:05:45 +0100310 @cmd2.with_category(CUSTOM_CATEGORY)
311 def do_equip(self, opts):
312 """Equip pySim-shell with card"""
Philipp Maiere6cba762023-08-11 11:23:17 +0200313 if self.rs and self.rs.profile:
Harald Welte659781c2023-06-06 17:00:51 +0200314 for cmd_set in self.rs.profile.shell_cmdsets:
315 self.unregister_command_set(cmd_set)
Philipp Maier91c971b2023-10-09 10:48:46 +0200316 rs, card = init_card(self.sl)
Harald Weltec91085e2022-02-10 18:05:45 +0100317 self.equip(card, rs)
Philipp Maier76667642021-09-22 16:53:22 +0200318
Philipp Maier7226c092022-06-01 17:58:38 +0200319 apdu_cmd_parser = argparse.ArgumentParser()
320 apdu_cmd_parser.add_argument('APDU', type=str, help='APDU as hex string')
Philipp Maiere7d1b672022-06-01 18:05:34 +0200321 apdu_cmd_parser.add_argument('--expect-sw', help='expect a specified status word', type=str, default=None)
Philipp Maier7226c092022-06-01 17:58:38 +0200322
323 @cmd2.with_argparser(apdu_cmd_parser)
324 def do_apdu(self, opts):
325 """Send a raw APDU to the card, and print SW + Response.
326 DANGEROUS: pySim-shell will not know any card state changes, and
327 not continue to work as expected if you e.g. select a different
328 file."""
Harald Welte46255122023-10-21 23:40:42 +0200329 data, sw = self.lchan.scc._tp.send_apdu(opts.APDU)
Philipp Maier7226c092022-06-01 17:58:38 +0200330 if data:
331 self.poutput("SW: %s, RESP: %s" % (sw, data))
332 else:
333 self.poutput("SW: %s" % sw)
Philipp Maiere7d1b672022-06-01 18:05:34 +0200334 if opts.expect_sw:
335 if not sw_match(sw, opts.expect_sw):
336 raise SwMatchError(sw, opts.expect_sw)
Philipp Maier7226c092022-06-01 17:58:38 +0200337
Harald Weltec91085e2022-02-10 18:05:45 +0100338 class InterceptStderr(list):
339 def __init__(self):
340 self._stderr_backup = sys.stderr
Philipp Maier76667642021-09-22 16:53:22 +0200341
Harald Weltec91085e2022-02-10 18:05:45 +0100342 def __enter__(self):
343 self._stringio_stderr = StringIO()
344 sys.stderr = self._stringio_stderr
345 return self
Philipp Maier76667642021-09-22 16:53:22 +0200346
Harald Weltec91085e2022-02-10 18:05:45 +0100347 def __exit__(self, *args):
348 self.stderr = self._stringio_stderr.getvalue().strip()
349 del self._stringio_stderr
350 sys.stderr = self._stderr_backup
Philipp Maier76667642021-09-22 16:53:22 +0200351
Harald Weltec91085e2022-02-10 18:05:45 +0100352 def _show_failure_sign(self):
Harald Welte961b8032023-04-27 17:30:22 +0200353 self.poutput(style(" +-------------+", fg=LIGHT_RED))
354 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))
Harald Weltec91085e2022-02-10 18:05:45 +0100360 self.poutput("")
Philipp Maier76667642021-09-22 16:53:22 +0200361
Harald Weltec91085e2022-02-10 18:05:45 +0100362 def _show_success_sign(self):
Harald Welte961b8032023-04-27 17:30:22 +0200363 self.poutput(style(" +-------------+", fg=LIGHT_GREEN))
364 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))
Harald Weltec91085e2022-02-10 18:05:45 +0100370 self.poutput("")
Philipp Maier76667642021-09-22 16:53:22 +0200371
Harald Weltec91085e2022-02-10 18:05:45 +0100372 def _process_card(self, first, script_path):
Philipp Maier76667642021-09-22 16:53:22 +0200373
Harald Weltec91085e2022-02-10 18:05:45 +0100374 # Early phase of card initialzation (this part may fail with an exception)
375 try:
376 rs, card = init_card(self.sl)
377 rc = self.equip(card, rs)
378 except:
379 self.poutput("")
Philipp Maier6bfa8a82023-10-09 13:32:49 +0200380 self.poutput("Card initialization (%s) failed with an exception:" % str(self.sl))
Harald Weltec91085e2022-02-10 18:05:45 +0100381 self.poutput("---------------------8<---------------------")
382 traceback.print_exc()
383 self.poutput("---------------------8<---------------------")
384 self.poutput("")
385 return -1
Philipp Maier76667642021-09-22 16:53:22 +0200386
Harald Weltec91085e2022-02-10 18:05:45 +0100387 # Actual card processing step. This part should never fail with an exception since the cmd2
388 # do_run_script method will catch any exception that might occur during script execution.
389 if rc:
390 self.poutput("")
391 self.poutput("Transcript stdout:")
392 self.poutput("---------------------8<---------------------")
393 with self.InterceptStderr() as logged:
394 self.do_run_script(script_path)
395 self.poutput("---------------------8<---------------------")
Philipp Maier76667642021-09-22 16:53:22 +0200396
Harald Weltec91085e2022-02-10 18:05:45 +0100397 self.poutput("")
398 self.poutput("Transcript stderr:")
399 if logged.stderr:
400 self.poutput("---------------------8<---------------------")
401 self.poutput(logged.stderr)
402 self.poutput("---------------------8<---------------------")
403 else:
404 self.poutput("(none)")
Philipp Maier76667642021-09-22 16:53:22 +0200405
Harald Weltec91085e2022-02-10 18:05:45 +0100406 # Check for exceptions
407 self.poutput("")
408 if "EXCEPTION of type" not in logged.stderr:
409 return 0
Philipp Maier76667642021-09-22 16:53:22 +0200410
Harald Weltec91085e2022-02-10 18:05:45 +0100411 return -1
Philipp Maier76667642021-09-22 16:53:22 +0200412
Harald Weltec91085e2022-02-10 18:05:45 +0100413 bulk_script_parser = argparse.ArgumentParser()
414 bulk_script_parser.add_argument(
415 'script_path', help="path to the script file")
416 bulk_script_parser.add_argument('--halt_on_error', help='stop card handling if an exeption occurs',
417 action='store_true')
418 bulk_script_parser.add_argument('--tries', type=int, default=2,
419 help='how many tries before trying the next card')
420 bulk_script_parser.add_argument('--on_stop_action', type=str, default=None,
421 help='commandline to execute when card handling has stopped')
422 bulk_script_parser.add_argument('--pre_card_action', type=str, default=None,
423 help='commandline to execute before actually talking to the card')
Philipp Maier76667642021-09-22 16:53:22 +0200424
Harald Weltec91085e2022-02-10 18:05:45 +0100425 @cmd2.with_argparser(bulk_script_parser)
426 @cmd2.with_category(CUSTOM_CATEGORY)
427 def do_bulk_script(self, opts):
428 """Run script on multiple cards (bulk provisioning)"""
Philipp Maier76667642021-09-22 16:53:22 +0200429
Harald Weltec91085e2022-02-10 18:05:45 +0100430 # Make sure that the script file exists and that it is readable.
431 if not os.access(opts.script_path, os.R_OK):
432 self.poutput("Invalid script file!")
433 return
Philipp Maier76667642021-09-22 16:53:22 +0200434
Harald Weltec91085e2022-02-10 18:05:45 +0100435 success_count = 0
436 fail_count = 0
Philipp Maier76667642021-09-22 16:53:22 +0200437
Harald Weltec91085e2022-02-10 18:05:45 +0100438 first = True
439 while 1:
440 # TODO: Count consecutive failures, if more than N consecutive failures occur, then stop.
441 # The ratinale is: There may be a problem with the device, we do want to prevent that
442 # all remaining cards are fired to the error bin. This is only relevant for situations
443 # with large stacks, probably we do not need this feature right now.
Philipp Maier76667642021-09-22 16:53:22 +0200444
Harald Weltec91085e2022-02-10 18:05:45 +0100445 try:
446 # In case of failure, try multiple times.
447 for i in range(opts.tries):
448 # fetch card into reader bay
Philipp Maier91c971b2023-10-09 10:48:46 +0200449 self.ch.get(first)
Philipp Maier76667642021-09-22 16:53:22 +0200450
Harald Weltec91085e2022-02-10 18:05:45 +0100451 # if necessary execute an action before we start processing the card
452 if(opts.pre_card_action):
453 os.system(opts.pre_card_action)
Philipp Maier76667642021-09-22 16:53:22 +0200454
Harald Weltec91085e2022-02-10 18:05:45 +0100455 # process the card
456 rc = self._process_card(first, opts.script_path)
457 if rc == 0:
458 success_count = success_count + 1
459 self._show_success_sign()
460 self.poutput("Statistics: success :%i, failure: %i" % (
461 success_count, fail_count))
462 break
463 else:
464 fail_count = fail_count + 1
465 self._show_failure_sign()
466 self.poutput("Statistics: success :%i, failure: %i" % (
467 success_count, fail_count))
Philipp Maier76667642021-09-22 16:53:22 +0200468
Harald Weltec91085e2022-02-10 18:05:45 +0100469 # Depending on success or failure, the card goes either in the "error" bin or in the
470 # "done" bin.
471 if rc < 0:
Philipp Maier91c971b2023-10-09 10:48:46 +0200472 self.ch.error()
Harald Weltec91085e2022-02-10 18:05:45 +0100473 else:
Philipp Maier91c971b2023-10-09 10:48:46 +0200474 self.ch.done()
Philipp Maier76667642021-09-22 16:53:22 +0200475
Harald Weltec91085e2022-02-10 18:05:45 +0100476 # In most cases it is possible to proceed with the next card, but the
477 # user may decide to halt immediately when an error occurs
478 if opts.halt_on_error and rc < 0:
479 return
Philipp Maier76667642021-09-22 16:53:22 +0200480
Harald Weltec91085e2022-02-10 18:05:45 +0100481 except (KeyboardInterrupt):
482 self.poutput("")
483 self.poutput("Terminated by user!")
484 return
485 except (SystemExit):
486 # When all cards are processed the card handler device will throw a SystemExit
487 # exception. Also Errors that are not recoverable (cards stuck etc.) will end up here.
488 # The user has the option to execute some action to make aware that the card handler
489 # needs service.
490 if(opts.on_stop_action):
491 os.system(opts.on_stop_action)
492 return
493 except:
494 self.poutput("")
Philipp Maier6bfa8a82023-10-09 13:32:49 +0200495 self.poutput("Card handling (%s) failed with an exception:" % str(self.sl))
Harald Weltec91085e2022-02-10 18:05:45 +0100496 self.poutput("---------------------8<---------------------")
497 traceback.print_exc()
498 self.poutput("---------------------8<---------------------")
499 self.poutput("")
500 fail_count = fail_count + 1
501 self._show_failure_sign()
502 self.poutput("Statistics: success :%i, failure: %i" %
503 (success_count, fail_count))
Philipp Maierb52feed2021-09-22 16:43:13 +0200504
Harald Weltec91085e2022-02-10 18:05:45 +0100505 first = False
506
507 echo_parser = argparse.ArgumentParser()
508 echo_parser.add_argument('string', help="string to echo on the shell")
509
510 @cmd2.with_argparser(echo_parser)
511 @cmd2.with_category(CUSTOM_CATEGORY)
512 def do_echo(self, opts):
513 """Echo (print) a string on the console"""
514 self.poutput(opts.string)
515
Harald Weltefc315482022-07-23 12:49:14 +0200516 @cmd2.with_category(CUSTOM_CATEGORY)
517 def do_version(self, opts):
518 """Print the pySim software version."""
519 import pkg_resources
520 self.poutput(pkg_resources.get_distribution('pySim'))
Harald Welteb2edd142021-01-08 23:29:35 +0100521
Harald Welte31d2cf02021-04-03 10:47:29 +0200522@with_default_category('pySim Commands')
523class PySimCommands(CommandSet):
Harald Weltec91085e2022-02-10 18:05:45 +0100524 def __init__(self):
525 super().__init__()
Harald Welteb2edd142021-01-08 23:29:35 +0100526
Harald Weltec91085e2022-02-10 18:05:45 +0100527 dir_parser = argparse.ArgumentParser()
528 dir_parser.add_argument(
529 '--fids', help='Show file identifiers', action='store_true')
530 dir_parser.add_argument(
531 '--names', help='Show file names', action='store_true')
532 dir_parser.add_argument(
533 '--apps', help='Show applications', action='store_true')
534 dir_parser.add_argument(
535 '--all', help='Show all selectable identifiers and names', action='store_true')
Philipp Maier5d3e2592021-02-22 17:22:16 +0100536
Harald Weltec91085e2022-02-10 18:05:45 +0100537 @cmd2.with_argparser(dir_parser)
538 def do_dir(self, opts):
539 """Show a listing of files available in currently selected DF or MF"""
540 if opts.all:
541 flags = []
542 elif opts.fids or opts.names or opts.apps:
543 flags = ['PARENT', 'SELF']
544 if opts.fids:
545 flags += ['FIDS', 'AIDS']
546 if opts.names:
547 flags += ['FNAMES', 'ANAMES']
548 if opts.apps:
549 flags += ['ANAMES', 'AIDS']
550 else:
551 flags = ['PARENT', 'SELF', 'FNAMES', 'ANAMES']
552 selectables = list(
Harald Weltea6c0f882022-07-17 14:23:17 +0200553 self._cmd.lchan.selected_file.get_selectable_names(flags=flags))
Harald Weltec91085e2022-02-10 18:05:45 +0100554 directory_str = tabulate_str_list(
555 selectables, width=79, hspace=2, lspace=1, align_left=True)
Harald Welteb2e4b4a2022-07-19 23:48:45 +0200556 path = self._cmd.lchan.selected_file.fully_qualified_path_str(True)
557 self._cmd.poutput(path)
558 path = self._cmd.lchan.selected_file.fully_qualified_path_str(False)
559 self._cmd.poutput(path)
Harald Weltec91085e2022-02-10 18:05:45 +0100560 self._cmd.poutput(directory_str)
561 self._cmd.poutput("%d files" % len(selectables))
Harald Welteb2edd142021-01-08 23:29:35 +0100562
Philipp Maier7b138b02022-05-31 13:42:56 +0200563 def walk(self, indent=0, action_ef=None, action_df=None, context=None, **kwargs):
Harald Weltec91085e2022-02-10 18:05:45 +0100564 """Recursively walk through the file system, starting at the currently selected DF"""
Philipp Maier7b138b02022-05-31 13:42:56 +0200565
Harald Weltea6c0f882022-07-17 14:23:17 +0200566 if isinstance(self._cmd.lchan.selected_file, CardDF):
Philipp Maier7b138b02022-05-31 13:42:56 +0200567 if action_df:
Philipp Maier91c971b2023-10-09 10:48:46 +0200568 action_df(context, **kwargs)
Philipp Maier7b138b02022-05-31 13:42:56 +0200569
Harald Weltea6c0f882022-07-17 14:23:17 +0200570 files = self._cmd.lchan.selected_file.get_selectables(
Harald Weltec91085e2022-02-10 18:05:45 +0100571 flags=['FNAMES', 'ANAMES'])
572 for f in files:
Philipp Maier7b138b02022-05-31 13:42:56 +0200573 # special case: When no action is performed, just output a directory
574 if not action_ef and not action_df:
Harald Weltec91085e2022-02-10 18:05:45 +0100575 output_str = " " * indent + str(f) + (" " * 250)
576 output_str = output_str[0:25]
577 if isinstance(files[f], CardADF):
578 output_str += " " + str(files[f].aid)
579 else:
580 output_str += " " + str(files[f].fid)
581 output_str += " " + str(files[f].desc)
582 self._cmd.poutput(output_str)
Philipp Maierf408a402021-04-09 21:16:12 +0200583
Harald Weltec91085e2022-02-10 18:05:45 +0100584 if isinstance(files[f], CardDF):
585 skip_df = False
586 try:
Harald Weltea6c0f882022-07-17 14:23:17 +0200587 fcp_dec = self._cmd.lchan.select(f, self._cmd)
Harald Weltec91085e2022-02-10 18:05:45 +0100588 except Exception as e:
589 skip_df = True
Harald Weltea6c0f882022-07-17 14:23:17 +0200590 df = self._cmd.lchan.selected_file
Harald Welteb2e4b4a2022-07-19 23:48:45 +0200591 df_path = df.fully_qualified_path_str(True)
592 df_skip_reason_str = df_path + \
Harald Weltec91085e2022-02-10 18:05:45 +0100593 "/" + str(f) + ", " + str(e)
594 if context:
595 context['DF_SKIP'] += 1
596 context['DF_SKIP_REASON'].append(df_skip_reason_str)
Philipp Maierf408a402021-04-09 21:16:12 +0200597
Harald Weltec91085e2022-02-10 18:05:45 +0100598 # If the DF was skipped, we never have entered the directory
599 # below, so we must not move up.
600 if skip_df == False:
Philipp Maier7b138b02022-05-31 13:42:56 +0200601 self.walk(indent + 1, action_ef, action_df, context, **kwargs)
Harald Weltea6c0f882022-07-17 14:23:17 +0200602 fcp_dec = self._cmd.lchan.select("..", self._cmd)
Philipp Maierf408a402021-04-09 21:16:12 +0200603
Philipp Maier7b138b02022-05-31 13:42:56 +0200604 elif action_ef:
Harald Weltea6c0f882022-07-17 14:23:17 +0200605 df_before_action = self._cmd.lchan.selected_file
Philipp Maier7b138b02022-05-31 13:42:56 +0200606 action_ef(f, context, **kwargs)
Harald Weltec91085e2022-02-10 18:05:45 +0100607 # When walking through the file system tree the action must not
608 # always restore the currently selected file to the file that
609 # was selected before executing the action() callback.
Harald Weltea6c0f882022-07-17 14:23:17 +0200610 if df_before_action != self._cmd.lchan.selected_file:
Harald Weltec91085e2022-02-10 18:05:45 +0100611 raise RuntimeError("inconsistent walk, %s is currently selected but expecting %s to be selected"
Harald Weltea6c0f882022-07-17 14:23:17 +0200612 % (str(self._cmd.lchan.selected_file), str(df_before_action)))
Philipp Maierff9dae22021-02-25 17:03:21 +0100613
Harald Weltec91085e2022-02-10 18:05:45 +0100614 def do_tree(self, opts):
615 """Display a filesystem-tree with all selectable files"""
616 self.walk()
Philipp Maierff9dae22021-02-25 17:03:21 +0100617
Philipp Maier7b138b02022-05-31 13:42:56 +0200618 def export_ef(self, filename, context, as_json):
619 """ Select and export a single elementary file (EF) """
Harald Weltec91085e2022-02-10 18:05:45 +0100620 context['COUNT'] += 1
Harald Weltea6c0f882022-07-17 14:23:17 +0200621 df = self._cmd.lchan.selected_file
Philipp Maierac34dcc2021-04-01 17:19:05 +0200622
Philipp Maierea81f752022-05-19 10:13:30 +0200623 # The currently selected file (not the file we are going to export)
624 # must always be an ADF or DF. From this starting point we select
625 # the EF we want to export. To maintain consistency we will then
626 # select the current DF again (see comment below).
Harald Weltec91085e2022-02-10 18:05:45 +0100627 if not isinstance(df, CardDF):
628 raise RuntimeError(
629 "currently selected file %s is not a DF or ADF" % str(df))
Philipp Maierac34dcc2021-04-01 17:19:05 +0200630
Harald Weltec91085e2022-02-10 18:05:45 +0100631 df_path_list = df.fully_qualified_path(True)
Harald Welteb2e4b4a2022-07-19 23:48:45 +0200632 df_path = df.fully_qualified_path_str(True)
633 df_path_fid = df.fully_qualified_path_str(False)
Philipp Maier24f7bd32021-02-25 17:06:18 +0100634
Harald Welteb2e4b4a2022-07-19 23:48:45 +0200635 file_str = df_path + "/" + str(filename)
Harald Weltec91085e2022-02-10 18:05:45 +0100636 self._cmd.poutput(boxed_heading_str(file_str))
Philipp Maier24f7bd32021-02-25 17:06:18 +0100637
Harald Welteb2e4b4a2022-07-19 23:48:45 +0200638 self._cmd.poutput("# directory: %s (%s)" % (df_path, df_path_fid))
Harald Weltec91085e2022-02-10 18:05:45 +0100639 try:
Harald Weltea6c0f882022-07-17 14:23:17 +0200640 fcp_dec = self._cmd.lchan.select(filename, self._cmd)
Harald Weltec91085e2022-02-10 18:05:45 +0100641 self._cmd.poutput("# file: %s (%s)" % (
Harald Weltea6c0f882022-07-17 14:23:17 +0200642 self._cmd.lchan.selected_file.name, self._cmd.lchan.selected_file.fid))
Philipp Maier24f7bd32021-02-25 17:06:18 +0100643
Harald Weltea6c0f882022-07-17 14:23:17 +0200644 structure = self._cmd.lchan.selected_file_structure()
Harald Weltec91085e2022-02-10 18:05:45 +0100645 self._cmd.poutput("# structure: %s" % str(structure))
Harald Weltea6c0f882022-07-17 14:23:17 +0200646 self._cmd.poutput("# RAW FCP Template: %s" % str(self._cmd.lchan.selected_file_fcp_hex))
647 self._cmd.poutput("# Decoded FCP Template: %s" % str(self._cmd.lchan.selected_file_fcp))
Philipp Maier24f7bd32021-02-25 17:06:18 +0100648
Harald Weltec91085e2022-02-10 18:05:45 +0100649 for f in df_path_list:
650 self._cmd.poutput("select " + str(f))
Harald Weltea6c0f882022-07-17 14:23:17 +0200651 self._cmd.poutput("select " + self._cmd.lchan.selected_file.name)
Philipp Maier24f7bd32021-02-25 17:06:18 +0100652
Harald Weltec91085e2022-02-10 18:05:45 +0100653 if structure == 'transparent':
Harald Welte08b11ab2022-02-10 18:56:41 +0100654 if as_json:
Harald Weltea6c0f882022-07-17 14:23:17 +0200655 result = self._cmd.lchan.read_binary_dec()
Harald Welte08b11ab2022-02-10 18:56:41 +0100656 self._cmd.poutput("update_binary_decoded '%s'" % json.dumps(result[0], cls=JsonEncoder))
657 else:
Harald Weltea6c0f882022-07-17 14:23:17 +0200658 result = self._cmd.lchan.read_binary()
Harald Welte08b11ab2022-02-10 18:56:41 +0100659 self._cmd.poutput("update_binary " + str(result[0]))
Harald Weltec91085e2022-02-10 18:05:45 +0100660 elif structure == 'cyclic' or structure == 'linear_fixed':
661 # Use number of records specified in select response
Harald Weltea6c0f882022-07-17 14:23:17 +0200662 num_of_rec = self._cmd.lchan.selected_file_num_of_rec()
Harald Welte747a9782022-02-13 17:52:28 +0100663 if num_of_rec:
Harald Weltec91085e2022-02-10 18:05:45 +0100664 for r in range(1, num_of_rec + 1):
Harald Welte08b11ab2022-02-10 18:56:41 +0100665 if as_json:
Harald Weltea6c0f882022-07-17 14:23:17 +0200666 result = self._cmd.lchan.read_record_dec(r)
Harald Welte08b11ab2022-02-10 18:56:41 +0100667 self._cmd.poutput("update_record_decoded %d '%s'" % (r, json.dumps(result[0], cls=JsonEncoder)))
668 else:
Harald Weltea6c0f882022-07-17 14:23:17 +0200669 result = self._cmd.lchan.read_record(r)
Harald Welte08b11ab2022-02-10 18:56:41 +0100670 self._cmd.poutput("update_record %d %s" % (r, str(result[0])))
671
Harald Weltec91085e2022-02-10 18:05:45 +0100672 # When the select response does not return the number of records, read until we hit the
673 # first record that cannot be read.
674 else:
675 r = 1
676 while True:
677 try:
Harald Welte08b11ab2022-02-10 18:56:41 +0100678 if as_json:
Harald Weltea6c0f882022-07-17 14:23:17 +0200679 result = self._cmd.lchan.read_record_dec(r)
Harald Welte08b11ab2022-02-10 18:56:41 +0100680 self._cmd.poutput("update_record_decoded %d '%s'" % (r, json.dumps(result[0], cls=JsonEncoder)))
681 else:
Harald Weltea6c0f882022-07-17 14:23:17 +0200682 result = self._cmd.lchan.read_record(r)
Harald Welte08b11ab2022-02-10 18:56:41 +0100683 self._cmd.poutput("update_record %d %s" % (r, str(result[0])))
Harald Weltec91085e2022-02-10 18:05:45 +0100684 except SwMatchError as e:
685 # We are past the last valid record - stop
686 if e.sw_actual == "9402":
687 break
688 # Some other problem occurred
689 else:
690 raise e
Harald Weltec91085e2022-02-10 18:05:45 +0100691 r = r + 1
692 elif structure == 'ber_tlv':
Harald Weltea6c0f882022-07-17 14:23:17 +0200693 tags = self._cmd.lchan.retrieve_tags()
Harald Weltec91085e2022-02-10 18:05:45 +0100694 for t in tags:
Harald Weltea6c0f882022-07-17 14:23:17 +0200695 result = self._cmd.lchan.retrieve_data(t)
Harald Weltec91085e2022-02-10 18:05:45 +0100696 (tag, l, val, remainer) = bertlv_parse_one(h2b(result[0]))
697 self._cmd.poutput("set_data 0x%02x %s" % (t, b2h(val)))
698 else:
699 raise RuntimeError(
700 'Unsupported structure "%s" of file "%s"' % (structure, filename))
701 except Exception as e:
Harald Welteb2e4b4a2022-07-19 23:48:45 +0200702 bad_file_str = df_path + "/" + str(filename) + ", " + str(e)
Harald Weltec91085e2022-02-10 18:05:45 +0100703 self._cmd.poutput("# bad file: %s" % bad_file_str)
704 context['ERR'] += 1
705 context['BAD'].append(bad_file_str)
Philipp Maier24f7bd32021-02-25 17:06:18 +0100706
Harald Weltec91085e2022-02-10 18:05:45 +0100707 # When reading the file is done, make sure the parent file is
708 # selected again. This will be the usual case, however we need
709 # to check before since we must not select the same DF twice
Harald Weltea6c0f882022-07-17 14:23:17 +0200710 if df != self._cmd.lchan.selected_file:
711 self._cmd.lchan.select(df.fid or df.aid, self._cmd)
Philipp Maierac34dcc2021-04-01 17:19:05 +0200712
Harald Weltec91085e2022-02-10 18:05:45 +0100713 self._cmd.poutput("#")
Philipp Maier24f7bd32021-02-25 17:06:18 +0100714
Harald Weltec91085e2022-02-10 18:05:45 +0100715 export_parser = argparse.ArgumentParser()
716 export_parser.add_argument(
717 '--filename', type=str, default=None, help='only export specific file')
Harald Welte08b11ab2022-02-10 18:56:41 +0100718 export_parser.add_argument(
719 '--json', action='store_true', help='export as JSON (less reliable)')
Philipp Maier24f7bd32021-02-25 17:06:18 +0100720
Harald Weltec91085e2022-02-10 18:05:45 +0100721 @cmd2.with_argparser(export_parser)
722 def do_export(self, opts):
723 """Export files to script that can be imported back later"""
724 context = {'ERR': 0, 'COUNT': 0, 'BAD': [],
725 'DF_SKIP': 0, 'DF_SKIP_REASON': []}
Philipp Maier9a4091d2022-05-19 10:20:30 +0200726 kwargs_export = {'as_json': opts.json}
Philipp Maierf16ac6a2022-05-31 14:08:47 +0200727 exception_str_add = ""
728
Harald Weltec91085e2022-02-10 18:05:45 +0100729 if opts.filename:
Philipp Maier7b138b02022-05-31 13:42:56 +0200730 self.export_ef(opts.filename, context, **kwargs_export)
Harald Weltec91085e2022-02-10 18:05:45 +0100731 else:
Philipp Maierf16ac6a2022-05-31 14:08:47 +0200732 try:
733 self.walk(0, self.export_ef, None, context, **kwargs_export)
734 except Exception as e:
735 print("# Stopping early here due to exception: " + str(e))
736 print("#")
737 exception_str_add = ", also had to stop early due to exception:" + str(e)
Philipp Maier80ce71f2021-04-19 21:24:23 +0200738
Harald Weltec91085e2022-02-10 18:05:45 +0100739 self._cmd.poutput(boxed_heading_str("Export summary"))
Philipp Maier80ce71f2021-04-19 21:24:23 +0200740
Harald Weltec91085e2022-02-10 18:05:45 +0100741 self._cmd.poutput("# total files visited: %u" % context['COUNT'])
742 self._cmd.poutput("# bad files: %u" % context['ERR'])
743 for b in context['BAD']:
744 self._cmd.poutput("# " + b)
Philipp Maierf408a402021-04-09 21:16:12 +0200745
Harald Weltec91085e2022-02-10 18:05:45 +0100746 self._cmd.poutput("# skipped dedicated files(s): %u" %
747 context['DF_SKIP'])
748 for b in context['DF_SKIP_REASON']:
749 self._cmd.poutput("# " + b)
Philipp Maierf408a402021-04-09 21:16:12 +0200750
Harald Weltec91085e2022-02-10 18:05:45 +0100751 if context['ERR'] and context['DF_SKIP']:
Philipp Maierf16ac6a2022-05-31 14:08:47 +0200752 raise RuntimeError("unable to export %i elementary file(s) and %i dedicated file(s)%s" % (
753 context['ERR'], context['DF_SKIP'], exception_str_add))
Harald Weltec91085e2022-02-10 18:05:45 +0100754 elif context['ERR']:
755 raise RuntimeError(
Philipp Maierf16ac6a2022-05-31 14:08:47 +0200756 "unable to export %i elementary file(s)%s" % (context['ERR'], exception_str_add))
Harald Weltec91085e2022-02-10 18:05:45 +0100757 elif context['DF_SKIP']:
758 raise RuntimeError(
Philipp Maierf16ac6a2022-05-31 14:08:47 +0200759 "unable to export %i dedicated files(s)%s" % (context['ERR'], exception_str_add))
Harald Welteb2edd142021-01-08 23:29:35 +0100760
Harald Weltec91085e2022-02-10 18:05:45 +0100761 def do_reset(self, opts):
762 """Reset the Card."""
Philipp Maiere345e112023-06-09 15:19:56 +0200763 atr = self._cmd.card.reset()
764 self._cmd.poutput('Card ATR: %s' % i2h(atr))
Harald Weltec91085e2022-02-10 18:05:45 +0100765 self._cmd.update_prompt()
Harald Weltedaf2b392021-05-03 23:17:29 +0200766
Harald Weltec91085e2022-02-10 18:05:45 +0100767 def do_desc(self, opts):
768 """Display human readable file description for the currently selected file"""
Harald Weltea6c0f882022-07-17 14:23:17 +0200769 desc = self._cmd.lchan.selected_file.desc
Harald Weltec91085e2022-02-10 18:05:45 +0100770 if desc:
771 self._cmd.poutput(desc)
772 else:
773 self._cmd.poutput("no description available")
Philipp Maiera8c9ea92021-09-16 12:51:46 +0200774
Harald Weltec91085e2022-02-10 18:05:45 +0100775 def do_verify_adm(self, arg):
776 """VERIFY the ADM1 PIN"""
777 if arg:
778 # use specified ADM-PIN
779 pin_adm = sanitize_pin_adm(arg)
780 else:
781 # try to find an ADM-PIN if none is specified
782 result = card_key_provider_get_field(
783 'ADM1', key='ICCID', value=self._cmd.iccid)
784 pin_adm = sanitize_pin_adm(result)
785 if pin_adm:
786 self._cmd.poutput(
787 "found ADM-PIN '%s' for ICCID '%s'" % (result, self._cmd.iccid))
788 else:
789 raise ValueError(
790 "cannot find ADM-PIN for ICCID '%s'" % (self._cmd.iccid))
Philipp Maiera8c9ea92021-09-16 12:51:46 +0200791
Harald Weltec91085e2022-02-10 18:05:45 +0100792 if pin_adm:
Harald Welte46255122023-10-21 23:40:42 +0200793 self._cmd.lchan.scc.verify_chv(self._cmd.card._adm_chv_num, h2b(pin_adm))
Harald Weltec91085e2022-02-10 18:05:45 +0100794 else:
795 raise ValueError("error: cannot authenticate, no adm-pin!")
796
Philipp Maier7b9e2442023-03-22 15:19:54 +0100797 def do_cardinfo(self, opts):
798 """Display information about the currently inserted card"""
799 self._cmd.poutput("Card info:")
800 self._cmd.poutput(" Name: %s" % self._cmd.card.name)
Harald Welte46255122023-10-21 23:40:42 +0200801 self._cmd.poutput(" ATR: %s" % b2h(self._cmd.lchan.scc.get_atr()))
Philipp Maier7b9e2442023-03-22 15:19:54 +0100802 self._cmd.poutput(" ICCID: %s" % self._cmd.iccid)
Harald Welte46255122023-10-21 23:40:42 +0200803 self._cmd.poutput(" Class-Byte: %s" % self._cmd.lchan.scc.cla_byte)
804 self._cmd.poutput(" Select-Ctrl: %s" % self._cmd.lchan.scc.sel_ctrl)
Philipp Maier7b9e2442023-03-22 15:19:54 +0100805 self._cmd.poutput(" AIDs:")
806 for a in self._cmd.rs.mf.applications:
807 self._cmd.poutput(" %s" % a)
Harald Welteb2edd142021-01-08 23:29:35 +0100808
Harald Welte31d2cf02021-04-03 10:47:29 +0200809@with_default_category('ISO7816 Commands')
810class Iso7816Commands(CommandSet):
Harald Weltec91085e2022-02-10 18:05:45 +0100811 def __init__(self):
812 super().__init__()
Harald Welte31d2cf02021-04-03 10:47:29 +0200813
Harald Weltec91085e2022-02-10 18:05:45 +0100814 def do_select(self, opts):
815 """SELECT a File (ADF/DF/EF)"""
816 if len(opts.arg_list) == 0:
Harald Welteb2e4b4a2022-07-19 23:48:45 +0200817 path = self._cmd.lchan.selected_file.fully_qualified_path_str(True)
818 path_fid = self._cmd.lchan.selected_file.fully_qualified_path_str(False)
819 self._cmd.poutput("currently selected file: %s (%s)" % (path, path_fid))
Harald Weltec91085e2022-02-10 18:05:45 +0100820 return
Harald Welte31d2cf02021-04-03 10:47:29 +0200821
Harald Weltec91085e2022-02-10 18:05:45 +0100822 path = opts.arg_list[0]
Harald Weltea6c0f882022-07-17 14:23:17 +0200823 fcp_dec = self._cmd.lchan.select(path, self._cmd)
Harald Weltec91085e2022-02-10 18:05:45 +0100824 self._cmd.update_prompt()
825 self._cmd.poutput_json(fcp_dec)
Harald Welte31d2cf02021-04-03 10:47:29 +0200826
Harald Weltec91085e2022-02-10 18:05:45 +0100827 def complete_select(self, text, line, begidx, endidx) -> List[str]:
828 """Command Line tab completion for SELECT"""
Harald Weltea6c0f882022-07-17 14:23:17 +0200829 index_dict = {1: self._cmd.lchan.selected_file.get_selectable_names()}
Harald Weltec91085e2022-02-10 18:05:45 +0100830 return self._cmd.index_based_complete(text, line, begidx, endidx, index_dict=index_dict)
Harald Welte31d2cf02021-04-03 10:47:29 +0200831
Harald Weltec91085e2022-02-10 18:05:45 +0100832 def get_code(self, code):
833 """Use code either directly or try to get it from external data source"""
834 auto = ('PIN1', 'PIN2', 'PUK1', 'PUK2')
Harald Welte31d2cf02021-04-03 10:47:29 +0200835
Harald Weltec91085e2022-02-10 18:05:45 +0100836 if str(code).upper() not in auto:
837 return sanitize_pin_adm(code)
Harald Welte31d2cf02021-04-03 10:47:29 +0200838
Harald Weltec91085e2022-02-10 18:05:45 +0100839 result = card_key_provider_get_field(
840 str(code), key='ICCID', value=self._cmd.iccid)
841 result = sanitize_pin_adm(result)
842 if result:
843 self._cmd.poutput("found %s '%s' for ICCID '%s'" %
844 (code.upper(), result, self._cmd.iccid))
845 else:
846 self._cmd.poutput("cannot find %s for ICCID '%s'" %
847 (code.upper(), self._cmd.iccid))
848 return result
Harald Welte31d2cf02021-04-03 10:47:29 +0200849
Harald Weltec91085e2022-02-10 18:05:45 +0100850 verify_chv_parser = argparse.ArgumentParser()
851 verify_chv_parser.add_argument(
852 '--pin-nr', type=int, default=1, help='PIN Number, 1=PIN1, 2=PIN2 or custom value (decimal)')
853 verify_chv_parser.add_argument(
854 '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 +0200855
Harald Weltec91085e2022-02-10 18:05:45 +0100856 @cmd2.with_argparser(verify_chv_parser)
857 def do_verify_chv(self, opts):
Harald Welte12af7932022-02-15 16:39:08 +0100858 """Verify (authenticate) using specified CHV (PIN) code, which is how the specifications
859 call it if you authenticate yourself using the specified PIN. There usually is at least PIN1 and
860 PIN2."""
Harald Weltec91085e2022-02-10 18:05:45 +0100861 pin = self.get_code(opts.pin_code)
Harald Welte46255122023-10-21 23:40:42 +0200862 (data, sw) = self._cmd.lchan.scc.verify_chv(opts.pin_nr, h2b(pin))
Harald Weltec91085e2022-02-10 18:05:45 +0100863 self._cmd.poutput("CHV verification successful")
Harald Welte31d2cf02021-04-03 10:47:29 +0200864
Harald Weltec91085e2022-02-10 18:05:45 +0100865 unblock_chv_parser = argparse.ArgumentParser()
866 unblock_chv_parser.add_argument(
867 '--pin-nr', type=int, default=1, help='PUK Number, 1=PIN1, 2=PIN2 or custom value (decimal)')
868 unblock_chv_parser.add_argument(
869 'puk_code', type=str, help='PUK code digits \"PUK1\" or \"PUK2\" to get PUK code from external data source')
870 unblock_chv_parser.add_argument(
871 '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 +0200872
Harald Weltec91085e2022-02-10 18:05:45 +0100873 @cmd2.with_argparser(unblock_chv_parser)
874 def do_unblock_chv(self, opts):
875 """Unblock PIN code using specified PUK code"""
876 new_pin = self.get_code(opts.new_pin_code)
877 puk = self.get_code(opts.puk_code)
Harald Welte46255122023-10-21 23:40:42 +0200878 (data, sw) = self._cmd.lchan.scc.unblock_chv(
Harald Weltec91085e2022-02-10 18:05:45 +0100879 opts.pin_nr, h2b(puk), h2b(new_pin))
880 self._cmd.poutput("CHV unblock successful")
Harald Welte31d2cf02021-04-03 10:47:29 +0200881
Harald Weltec91085e2022-02-10 18:05:45 +0100882 change_chv_parser = argparse.ArgumentParser()
883 change_chv_parser.add_argument(
884 '--pin-nr', type=int, default=1, help='PUK Number, 1=PIN1, 2=PIN2 or custom value (decimal)')
885 change_chv_parser.add_argument(
886 'pin_code', type=str, help='PIN code digits \"PIN1\" or \"PIN2\" to get PIN code from external data source')
887 change_chv_parser.add_argument(
888 '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 +0200889
Harald Weltec91085e2022-02-10 18:05:45 +0100890 @cmd2.with_argparser(change_chv_parser)
891 def do_change_chv(self, opts):
892 """Change PIN code to a new PIN code"""
893 new_pin = self.get_code(opts.new_pin_code)
894 pin = self.get_code(opts.pin_code)
Harald Welte46255122023-10-21 23:40:42 +0200895 (data, sw) = self._cmd.lchan.scc.change_chv(
Harald Weltec91085e2022-02-10 18:05:45 +0100896 opts.pin_nr, h2b(pin), h2b(new_pin))
897 self._cmd.poutput("CHV change successful")
Harald Welte31d2cf02021-04-03 10:47:29 +0200898
Harald Weltec91085e2022-02-10 18:05:45 +0100899 disable_chv_parser = argparse.ArgumentParser()
900 disable_chv_parser.add_argument(
901 '--pin-nr', type=int, default=1, help='PIN Number, 1=PIN1, 2=PIN2 or custom value (decimal)')
902 disable_chv_parser.add_argument(
903 '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 +0200904
Harald Weltec91085e2022-02-10 18:05:45 +0100905 @cmd2.with_argparser(disable_chv_parser)
906 def do_disable_chv(self, opts):
907 """Disable PIN code using specified PIN code"""
908 pin = self.get_code(opts.pin_code)
Harald Welte46255122023-10-21 23:40:42 +0200909 (data, sw) = self._cmd.lchan.scc.disable_chv(opts.pin_nr, h2b(pin))
Harald Weltec91085e2022-02-10 18:05:45 +0100910 self._cmd.poutput("CHV disable successful")
Harald Welte31d2cf02021-04-03 10:47:29 +0200911
Harald Weltec91085e2022-02-10 18:05:45 +0100912 enable_chv_parser = argparse.ArgumentParser()
913 enable_chv_parser.add_argument(
914 '--pin-nr', type=int, default=1, help='PIN Number, 1=PIN1, 2=PIN2 or custom value (decimal)')
915 enable_chv_parser.add_argument(
916 '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 +0200917
Harald Weltec91085e2022-02-10 18:05:45 +0100918 @cmd2.with_argparser(enable_chv_parser)
919 def do_enable_chv(self, opts):
920 """Enable PIN code using specified PIN code"""
921 pin = self.get_code(opts.pin_code)
Harald Welte46255122023-10-21 23:40:42 +0200922 (data, sw) = self._cmd.lchan.scc.enable_chv(opts.pin_nr, h2b(pin))
Harald Weltec91085e2022-02-10 18:05:45 +0100923 self._cmd.poutput("CHV enable successful")
Harald Welte31d2cf02021-04-03 10:47:29 +0200924
Harald Weltec91085e2022-02-10 18:05:45 +0100925 def do_deactivate_file(self, opts):
Harald Welte799c3542022-02-15 15:56:28 +0100926 """Deactivate the currently selected EF"""
Harald Welte46255122023-10-21 23:40:42 +0200927 (data, sw) = self._cmd.lchan.scc.deactivate_file()
Harald Weltea4631612021-04-10 18:17:55 +0200928
Harald Welte799c3542022-02-15 15:56:28 +0100929 activate_file_parser = argparse.ArgumentParser()
930 activate_file_parser.add_argument('NAME', type=str, help='File name or FID of file to activate')
931 @cmd2.with_argparser(activate_file_parser)
Harald Weltec91085e2022-02-10 18:05:45 +0100932 def do_activate_file(self, opts):
Harald Welte12af7932022-02-15 16:39:08 +0100933 """Activate the specified EF. This used to be called REHABILITATE in TS 11.11 for classic
934 SIM. You need to specify the name or FID of the file to activate."""
Harald Weltea6c0f882022-07-17 14:23:17 +0200935 (data, sw) = self._cmd.lchan.activate_file(opts.NAME)
Harald Welte485692b2021-05-25 22:21:44 +0200936
Harald Weltec91085e2022-02-10 18:05:45 +0100937 def complete_activate_file(self, text, line, begidx, endidx) -> List[str]:
938 """Command Line tab completion for ACTIVATE FILE"""
Harald Weltea6c0f882022-07-17 14:23:17 +0200939 index_dict = {1: self._cmd.lchan.selected_file.get_selectable_names()}
Harald Weltec91085e2022-02-10 18:05:45 +0100940 return self._cmd.index_based_complete(text, line, begidx, endidx, index_dict=index_dict)
Harald Welte31d2cf02021-04-03 10:47:29 +0200941
Harald Weltec91085e2022-02-10 18:05:45 +0100942 open_chan_parser = argparse.ArgumentParser()
943 open_chan_parser.add_argument(
944 'chan_nr', type=int, default=0, help='Channel Number')
Harald Welte703f9332021-04-10 18:39:32 +0200945
Harald Weltec91085e2022-02-10 18:05:45 +0100946 @cmd2.with_argparser(open_chan_parser)
947 def do_open_channel(self, opts):
948 """Open a logical channel."""
Harald Welte46255122023-10-21 23:40:42 +0200949 (data, sw) = self._cmd.lchan.scc.manage_channel(
Harald Weltec91085e2022-02-10 18:05:45 +0100950 mode='open', lchan_nr=opts.chan_nr)
Harald Weltebdf59572023-10-21 20:06:19 +0200951 # this is executed only in successful case, as unsuccessful raises exception
952 self._cmd.lchan.add_lchan(opts.chan_nr)
Harald Welte703f9332021-04-10 18:39:32 +0200953
Harald Weltec91085e2022-02-10 18:05:45 +0100954 close_chan_parser = argparse.ArgumentParser()
955 close_chan_parser.add_argument(
956 'chan_nr', type=int, default=0, help='Channel Number')
Harald Welte703f9332021-04-10 18:39:32 +0200957
Harald Weltec91085e2022-02-10 18:05:45 +0100958 @cmd2.with_argparser(close_chan_parser)
959 def do_close_channel(self, opts):
960 """Close a logical channel."""
Harald Welte46255122023-10-21 23:40:42 +0200961 (data, sw) = self._cmd.lchan.scc.manage_channel(
Harald Weltec91085e2022-02-10 18:05:45 +0100962 mode='close', lchan_nr=opts.chan_nr)
Harald Weltebdf59572023-10-21 20:06:19 +0200963 # this is executed only in successful case, as unsuccessful raises exception
964 self._cmd.rs.del_lchan(opts.chan_nr)
Harald Welte703f9332021-04-10 18:39:32 +0200965
Harald Welte20650992023-10-21 23:47:02 +0200966 switch_chan_parser = argparse.ArgumentParser()
967 switch_chan_parser.add_argument(
968 'chan_nr', type=int, default=0, help='Channel Number')
969
970 @cmd2.with_argparser(switch_chan_parser)
971 def do_switch_channel(self, opts):
972 """Switch currently active logical channel."""
973 self._cmd.lchan._select_pre(self._cmd)
974 self._cmd.lchan = self._cmd.rs.lchan[opts.chan_nr]
975 self._cmd.lchan._select_post(self._cmd)
976 self._cmd.update_prompt()
977
Harald Weltec91085e2022-02-10 18:05:45 +0100978 def do_status(self, opts):
979 """Perform the STATUS command."""
Harald Weltea6c0f882022-07-17 14:23:17 +0200980 fcp_dec = self._cmd.lchan.status()
Harald Weltec91085e2022-02-10 18:05:45 +0100981 self._cmd.poutput_json(fcp_dec)
Harald Welte34b05d32021-05-25 22:03:13 +0200982
Harald Welteec950532021-10-20 13:09:00 +0200983
Harald Weltecab26c72022-08-06 16:12:30 +0200984class Proact(ProactiveHandler):
985 def receive_fetch(self, pcmd: ProactiveCommand):
986 # print its parsed representation
987 print(pcmd.decoded)
988 # TODO: implement the basics, such as SMS Sending, ...
989
990
Harald Welte703f9332021-04-10 18:39:32 +0200991
Harald Weltef422eb12023-06-09 11:15:09 +0200992option_parser = argparse.ArgumentParser(description='interactive SIM card shell',
Harald Weltef2e761c2021-04-11 11:56:44 +0200993 formatter_class=argparse.ArgumentDefaultsHelpFormatter)
Harald Welte28c24312021-04-11 12:19:36 +0200994argparse_add_reader_args(option_parser)
Harald Weltec8ff0262021-04-11 12:06:13 +0200995
996global_group = option_parser.add_argument_group('General Options')
997global_group.add_argument('--script', metavar='PATH', default=None,
Harald Welte28c24312021-04-11 12:19:36 +0200998 help='script with pySim-shell commands to be executed automatically at start-up')
Harald Weltec91085e2022-02-10 18:05:45 +0100999global_group.add_argument('--csv', metavar='FILE',
1000 default=None, help='Read card data from CSV file')
Philipp Maier76667642021-09-22 16:53:22 +02001001global_group.add_argument("--card_handler", dest="card_handler_config", metavar="FILE",
Harald Weltec91085e2022-02-10 18:05:45 +01001002 help="Use automatic card handling machine")
Harald Weltec8ff0262021-04-11 12:06:13 +02001003
1004adm_group = global_group.add_mutually_exclusive_group()
1005adm_group.add_argument('-a', '--pin-adm', metavar='PIN_ADM1', dest='pin_adm', default=None,
1006 help='ADM PIN used for provisioning (overwrites default)')
1007adm_group.add_argument('-A', '--pin-adm-hex', metavar='PIN_ADM1_HEX', dest='pin_adm_hex', default=None,
1008 help='ADM PIN used for provisioning, as hex string (16 characters long)')
Harald Welteb2edd142021-01-08 23:29:35 +01001009
Harald Welte38306df2023-07-11 21:17:55 +02001010option_parser.add_argument("command", nargs='?',
1011 help="A pySim-shell command that would optionally be executed at startup")
1012option_parser.add_argument('command_args', nargs=argparse.REMAINDER,
1013 help="Optional Arguments for command")
1014
Harald Welteb2edd142021-01-08 23:29:35 +01001015
1016if __name__ == '__main__':
1017
Harald Weltec91085e2022-02-10 18:05:45 +01001018 # Parse options
1019 opts = option_parser.parse_args()
Harald Welteb2edd142021-01-08 23:29:35 +01001020
Harald Weltec91085e2022-02-10 18:05:45 +01001021 # If a script file is specified, be sure that it actually exists
1022 if opts.script:
1023 if not os.access(opts.script, os.R_OK):
1024 print("Invalid script file!")
1025 sys.exit(2)
Philipp Maier13e258d2021-04-08 17:48:49 +02001026
Harald Weltec91085e2022-02-10 18:05:45 +01001027 # Register csv-file as card data provider, either from specified CSV
1028 # or from CSV file in home directory
1029 csv_default = str(Path.home()) + "/.osmocom/pysim/card_data.csv"
1030 if opts.csv:
1031 card_key_provider_register(CardKeyProviderCsv(opts.csv))
1032 if os.path.isfile(csv_default):
1033 card_key_provider_register(CardKeyProviderCsv(csv_default))
Philipp Maier2b11c322021-03-17 12:37:39 +01001034
Harald Weltec91085e2022-02-10 18:05:45 +01001035 # Init card reader driver
Harald Weltecab26c72022-08-06 16:12:30 +02001036 sl = init_reader(opts, proactive_handler = Proact())
Philipp Maierea95c392021-09-16 13:10:19 +02001037
Harald Weltec91085e2022-02-10 18:05:45 +01001038 # Create a card handler (for bulk provisioning)
1039 if opts.card_handler_config:
1040 ch = CardHandlerAuto(None, opts.card_handler_config)
1041 else:
1042 ch = CardHandler(sl)
Philipp Maier76667642021-09-22 16:53:22 +02001043
Harald Weltec91085e2022-02-10 18:05:45 +01001044 # Detect and initialize the card in the reader. This may fail when there
1045 # is no card in the reader or the card is unresponsive. PysimApp is
1046 # able to tolerate and recover from that.
1047 try:
1048 rs, card = init_card(sl)
1049 app = PysimApp(card, rs, sl, ch, opts.script)
1050 except:
Philipp Maier6bfa8a82023-10-09 13:32:49 +02001051 print("Card initialization (%s) failed with an exception:" % str(sl))
Harald Weltec91085e2022-02-10 18:05:45 +01001052 print("---------------------8<---------------------")
1053 traceback.print_exc()
1054 print("---------------------8<---------------------")
1055 print("(you may still try to recover from this manually by using the 'equip' command.)")
1056 print(
1057 " it should also be noted that some readers may behave strangely when no card")
1058 print(" is inserted.)")
1059 print("")
Philipp Maier8e03f2f2023-10-09 13:27:59 +02001060 app = PysimApp(None, None, sl, ch, opts.script)
Philipp Maierea95c392021-09-16 13:10:19 +02001061
Harald Weltec91085e2022-02-10 18:05:45 +01001062 # If the user supplies an ADM PIN at via commandline args authenticate
1063 # immediately so that the user does not have to use the shell commands
1064 pin_adm = sanitize_pin_adm(opts.pin_adm, opts.pin_adm_hex)
1065 if pin_adm:
1066 if not card:
1067 print("Card error, cannot do ADM verification with supplied ADM pin now.")
1068 try:
Philipp Maier4840d4d2023-09-06 14:54:14 +02001069 card._scc.verify_chv(card._adm_chv_num, h2b(pin_adm))
Harald Weltec91085e2022-02-10 18:05:45 +01001070 except Exception as e:
1071 print(e)
Philipp Maier228c98e2021-03-10 20:14:06 +01001072
Harald Welte38306df2023-07-11 21:17:55 +02001073 if opts.command:
1074 app.onecmd_plus_hooks('{} {}'.format(opts.command, ' '.join(opts.command_args)))
1075 else:
1076 app.cmdloop()