blob: 306dd405ca6b31be77c440218c98eab5e8352366 [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
Harald Welte4e59d892023-11-01 23:40:07 +010056from pySim.utils import is_hexstr_or_decimal, is_hexstr
Philipp Maier76667642021-09-22 16:53:22 +020057from pySim.card_handler import CardHandler, CardHandlerAuto
Harald Welteb2edd142021-01-08 23:29:35 +010058
Harald Welte531894d2023-07-11 19:11:11 +020059from pySim.filesystem import CardDF, CardADF, CardModel, CardApplication
60from pySim.runtime import RuntimeState
Philipp Maiera028c7d2021-11-08 16:12:03 +010061from pySim.profile import CardProfile
Vadim Yanitskiy87dd0202023-04-22 20:45:29 +070062from pySim.cdma_ruim import CardProfileRUIM
Harald Welteb2edd142021-01-08 23:29:35 +010063from pySim.ts_102_221 import CardProfileUICC
Harald Welte3c9b7842021-10-19 21:44:24 +020064from pySim.ts_102_222 import Ts102222Commands
Harald Welte2a33ad22021-10-20 10:14:18 +020065from pySim.gsm_r import DF_EIRENE
Harald Weltecab26c72022-08-06 16:12:30 +020066from pySim.cat import ProactiveCommand
Harald Welteb2edd142021-01-08 23:29:35 +010067
Harald Welte4c1dca02021-10-14 17:48:25 +020068# we need to import this module so that the SysmocomSJA2 sub-class of
69# CardModel is created, which will add the ATR-based matching and
70# calling of SysmocomSJA2.add_files. See CardModel.apply_matching_models
Harald Weltef44256c2021-10-14 15:53:39 +020071import pySim.sysmocom_sja2
72
Harald Welte6ad9a242023-07-11 18:55:29 +020073# we need to import these modules so that the various sub-classes of
74# CardProfile are created, which will be used in init_card() to iterate
75# over all known CardProfile sub-classes.
76import pySim.ts_31_102
77import pySim.ts_31_103
78import pySim.ts_31_104
79import pySim.ara_m
80import pySim.global_platform
Harald Welte268a2022023-10-22 13:12:11 +020081import pySim.euicc
Harald Welte6ad9a242023-07-11 18:55:29 +020082
Harald Welte4442b3d2021-04-03 09:00:16 +020083from pySim.card_key_provider import CardKeyProviderCsv, card_key_provider_register, card_key_provider_get_field
Philipp Maier2b11c322021-03-17 12:37:39 +010084
Harald Weltec91085e2022-02-10 18:05:45 +010085
Philipp Maierea95c392021-09-16 13:10:19 +020086def init_card(sl):
Harald Weltec91085e2022-02-10 18:05:45 +010087 """
88 Detect card in reader and setup card profile and runtime state. This
89 function must be called at least once on startup. The card and runtime
90 state object (rs) is required for all pySim-shell commands.
91 """
Philipp Maierea95c392021-09-16 13:10:19 +020092
Philipp Maier91c971b2023-10-09 10:48:46 +020093 # Create command layer
94 scc = SimCardCommands(transport=sl)
95
Harald Weltec91085e2022-02-10 18:05:45 +010096 # Wait up to three seconds for a card in reader and try to detect
97 # the card type.
98 print("Waiting for card...")
99 try:
100 sl.wait_for_card(3)
101 except NoCardError:
102 print("No card detected!")
103 return None, None
104 except:
105 print("Card not readable!")
106 return None, None
Philipp Maierea95c392021-09-16 13:10:19 +0200107
Philipp Maier24031252022-06-14 16:16:42 +0200108 generic_card = False
Harald Weltef8d2e2b2023-07-09 17:58:38 +0200109 card = card_detect(scc)
Harald Weltec91085e2022-02-10 18:05:45 +0100110 if card is None:
111 print("Warning: Could not detect card type - assuming a generic card type...")
Harald Weltef8d2e2b2023-07-09 17:58:38 +0200112 card = SimCardBase(scc)
Philipp Maier24031252022-06-14 16:16:42 +0200113 generic_card = True
Philipp Maiera028c7d2021-11-08 16:12:03 +0100114
Harald Weltec91085e2022-02-10 18:05:45 +0100115 profile = CardProfile.pick(scc)
116 if profile is None:
117 print("Unsupported card type!")
Philipp Maier7226c092022-06-01 17:58:38 +0200118 return None, card
Philipp Maierea95c392021-09-16 13:10:19 +0200119
Philipp Maier24031252022-06-14 16:16:42 +0200120 # ETSI TS 102 221, Table 9.3 specifies a default for the PIN key
121 # references, however card manufactures may still decide to pick an
122 # arbitrary key reference. In case we run on a generic card class that is
123 # detected as an UICC, we will pick the key reference that is officially
124 # specified.
125 if generic_card and isinstance(profile, CardProfileUICC):
126 card._adm_chv_num = 0x0A
127
Harald Weltec91085e2022-02-10 18:05:45 +0100128 print("Info: Card is of type: %s" % str(profile))
Philipp Maiera028c7d2021-11-08 16:12:03 +0100129
Harald Welte6ad9a242023-07-11 18:55:29 +0200130 # FIXME: this shouldn't really be here but somewhere else/more generic.
131 # We cannot do it within pySim/profile.py as that would create circular
132 # dependencies between the individual profiles and profile.py.
Harald Weltec91085e2022-02-10 18:05:45 +0100133 if isinstance(profile, CardProfileUICC):
Harald Welte6ad9a242023-07-11 18:55:29 +0200134 for app_cls in CardApplication.__subclasses__():
135 constr_sig = inspect.signature(app_cls.__init__)
136 # skip any intermediary sub-classes such as CardApplicationSD
137 if len(constr_sig.parameters) != 1:
138 continue
139 profile.add_application(app_cls())
Harald Weltef4a01472023-07-04 21:14:23 +0200140 # We have chosen SimCard() above, but we now know it actually is an UICC
141 # so it's safe to assume it supports USIM application (which we're adding above).
142 # IF we don't do this, we will have a SimCard but try USIM specific commands like
143 # the update_ust method (see https://osmocom.org/issues/6055)
144 if generic_card:
Harald Weltef8d2e2b2023-07-09 17:58:38 +0200145 card = UiccCardBase(scc)
Philipp Maiera028c7d2021-11-08 16:12:03 +0100146
Harald Weltec91085e2022-02-10 18:05:45 +0100147 # Create runtime state with card profile
148 rs = RuntimeState(card, profile)
Philipp Maierea95c392021-09-16 13:10:19 +0200149
Harald Weltec91085e2022-02-10 18:05:45 +0100150 CardModel.apply_matching_models(scc, rs)
Harald Weltef44256c2021-10-14 15:53:39 +0200151
Harald Weltec91085e2022-02-10 18:05:45 +0100152 # inform the transport that we can do context-specific SW interpretation
153 sl.set_sw_interpreter(rs)
Philipp Maierea95c392021-09-16 13:10:19 +0200154
Harald Weltec91085e2022-02-10 18:05:45 +0100155 return rs, card
156
Philipp Maier2b11c322021-03-17 12:37:39 +0100157
Harald Weltee1268722023-06-24 07:57:08 +0200158class Cmd2Compat(cmd2.Cmd):
159 """Backwards-compatibility wrapper around cmd2.Cmd to support older and newer
160 releases. See https://github.com/python-cmd2/cmd2/blob/master/CHANGELOG.md"""
161 def run_editor(self, file_path: Optional[str] = None) -> None:
162 if version.parse(cmd2.__version__) < version.parse("2.0.0"):
Harald Welte0ec01502023-06-24 10:00:12 +0200163 return self._run_editor(file_path) # pylint: disable=no-member
Harald Weltee1268722023-06-24 07:57:08 +0200164 else:
Harald Welte0ec01502023-06-24 10:00:12 +0200165 return super().run_editor(file_path) # pylint: disable=no-member
166
167class Settable2Compat(cmd2.Settable):
168 """Backwards-compatibility wrapper around cmd2.Settable to support older and newer
169 releases. See https://github.com/python-cmd2/cmd2/blob/master/CHANGELOG.md"""
170 def __init__(self, name, val_type, description, settable_object, **kwargs):
171 if version.parse(cmd2.__version__) < version.parse("2.0.0"):
172 super().__init__(name, val_type, description, **kwargs) # pylint: disable=no-value-for-parameter
173 else:
174 super().__init__(name, val_type, description, settable_object, **kwargs) # pylint: disable=too-many-function-args
Harald Weltee1268722023-06-24 07:57:08 +0200175
176class PysimApp(Cmd2Compat):
Harald Weltec91085e2022-02-10 18:05:45 +0100177 CUSTOM_CATEGORY = 'pySim Commands'
Harald Welte0ba3fd92023-11-01 19:18:24 +0100178 BANNER = """Welcome to pySim-shell!
179(C) 2021-2023 by Harald Welte, sysmocom - s.f.m.c. GmbH and contributors
180Online manual available at https://downloads.osmocom.org/docs/pysim/master/html/shell.html """
Philipp Maier76667642021-09-22 16:53:22 +0200181
Harald Weltec91085e2022-02-10 18:05:45 +0100182 def __init__(self, card, rs, sl, ch, script=None):
Harald Weltec85d4062023-04-27 17:10:17 +0200183 if version.parse(cmd2.__version__) < version.parse("2.0.0"):
184 kwargs = {'use_ipython': True}
185 else:
186 kwargs = {'include_ipy': True}
187
Oliver Smithe47ea5f2023-05-23 12:54:15 +0200188 # pylint: disable=unexpected-keyword-arg
Harald Weltec91085e2022-02-10 18:05:45 +0100189 super().__init__(persistent_history_file='~/.pysim_shell_history', allow_cli_args=False,
Harald Weltec85d4062023-04-27 17:10:17 +0200190 auto_load_commands=False, startup_script=script, **kwargs)
Harald Welte0ba3fd92023-11-01 19:18:24 +0100191 self.intro = style(self.BANNER, fg=RED)
Harald Weltec91085e2022-02-10 18:05:45 +0100192 self.default_category = 'pySim-shell built-in commands'
193 self.card = None
194 self.rs = None
Harald Weltea6c0f882022-07-17 14:23:17 +0200195 self.lchan = None
196 self.py_locals = {'card': self.card, 'rs': self.rs, 'lchan': self.lchan}
Harald Weltec91085e2022-02-10 18:05:45 +0100197 self.sl = sl
198 self.ch = ch
Harald Welte1748b932021-04-06 21:12:25 +0200199
Harald Weltec91085e2022-02-10 18:05:45 +0100200 self.numeric_path = False
Harald Weltec91085e2022-02-10 18:05:45 +0100201 self.conserve_write = True
Harald Weltec91085e2022-02-10 18:05:45 +0100202 self.json_pretty_print = True
Harald Weltec91085e2022-02-10 18:05:45 +0100203 self.apdu_trace = False
Philipp Maier5d698e52021-09-16 13:18:01 +0200204
Harald Welte0ec01502023-06-24 10:00:12 +0200205 self.add_settable(Settable2Compat('numeric_path', bool, 'Print File IDs instead of names', self,
206 onchange_cb=self._onchange_numeric_path))
207 self.add_settable(Settable2Compat('conserve_write', bool, 'Read and compare before write', self,
208 onchange_cb=self._onchange_conserve_write))
209 self.add_settable(Settable2Compat('json_pretty_print', bool, 'Pretty-Print JSON output', self))
210 self.add_settable(Settable2Compat('apdu_trace', bool, 'Trace and display APDUs exchanged with card', self,
211 onchange_cb=self._onchange_apdu_trace))
Harald Weltec91085e2022-02-10 18:05:45 +0100212 self.equip(card, rs)
Philipp Maier5d698e52021-09-16 13:18:01 +0200213
Harald Weltec91085e2022-02-10 18:05:45 +0100214 def equip(self, card, rs):
215 """
216 Equip pySim-shell with the supplied card and runtime state, add (or remove) all required settables and
217 and commands to enable card operations.
218 """
Philipp Maier76667642021-09-22 16:53:22 +0200219
Harald Weltec91085e2022-02-10 18:05:45 +0100220 rc = False
Philipp Maier5d698e52021-09-16 13:18:01 +0200221
Harald Weltec91085e2022-02-10 18:05:45 +0100222 # Unequip everything from pySim-shell that would not work in unequipped state
223 if self.rs:
Harald Weltea6c0f882022-07-17 14:23:17 +0200224 lchan = self.rs.lchan[0]
225 lchan.unregister_cmds(self)
iw0f818acd2023-06-19 10:27:04 +0200226 if self.rs.profile:
227 for cmd_set in self.rs.profile.shell_cmdsets:
228 self.unregister_command_set(cmd_set)
229
Harald Welte892526f2023-06-06 17:09:50 +0200230 for cmds in [Iso7816Commands, Ts102222Commands, PySimCommands]:
Harald Weltec91085e2022-02-10 18:05:45 +0100231 cmd_set = self.find_commandsets(cmds)
232 if cmd_set:
233 self.unregister_command_set(cmd_set[0])
Philipp Maier5d698e52021-09-16 13:18:01 +0200234
Harald Weltec91085e2022-02-10 18:05:45 +0100235 self.card = card
236 self.rs = rs
Philipp Maier5d698e52021-09-16 13:18:01 +0200237
Harald Weltec91085e2022-02-10 18:05:45 +0100238 # When a card object and a runtime state is present, (re)equip pySim-shell with everything that is
239 # needed to operate on cards.
240 if self.card and self.rs:
Harald Weltea6c0f882022-07-17 14:23:17 +0200241 self.lchan = self.rs.lchan[0]
Harald Weltec91085e2022-02-10 18:05:45 +0100242 self._onchange_conserve_write(
243 'conserve_write', False, self.conserve_write)
244 self._onchange_apdu_trace('apdu_trace', False, self.apdu_trace)
Harald Welte659781c2023-06-06 17:00:51 +0200245 if self.rs.profile:
246 for cmd_set in self.rs.profile.shell_cmdsets:
247 self.register_command_set(cmd_set)
Harald Weltec91085e2022-02-10 18:05:45 +0100248 self.register_command_set(Iso7816Commands())
Harald Welte3c9b7842021-10-19 21:44:24 +0200249 self.register_command_set(Ts102222Commands())
Harald Weltec91085e2022-02-10 18:05:45 +0100250 self.register_command_set(PySimCommands())
Philipp Maiera42ee6f2023-08-16 10:44:57 +0200251
Philipp Maier7c0cd0a2023-10-16 15:02:07 +0200252 try:
253 self.lchan.select('MF/EF.ICCID', self)
254 self.iccid = dec_iccid(self.lchan.read_binary()[0])
255 except:
256 self.iccid = None
Philipp Maiera42ee6f2023-08-16 10:44:57 +0200257
Harald Weltea6c0f882022-07-17 14:23:17 +0200258 self.lchan.select('MF', self)
Harald Weltec91085e2022-02-10 18:05:45 +0100259 rc = True
260 else:
261 self.poutput("pySim-shell not equipped!")
Philipp Maier5d698e52021-09-16 13:18:01 +0200262
Harald Weltec91085e2022-02-10 18:05:45 +0100263 self.update_prompt()
264 return rc
Harald Welteb2edd142021-01-08 23:29:35 +0100265
Harald Weltec91085e2022-02-10 18:05:45 +0100266 def poutput_json(self, data, force_no_pretty=False):
267 """like cmd2.poutput() but for a JSON serializable dict."""
268 if force_no_pretty or self.json_pretty_print == False:
269 output = json.dumps(data, cls=JsonEncoder)
270 else:
271 output = json.dumps(data, cls=JsonEncoder, indent=4)
272 self.poutput(output)
Harald Welteb2edd142021-01-08 23:29:35 +0100273
Harald Weltec91085e2022-02-10 18:05:45 +0100274 def _onchange_numeric_path(self, param_name, old, new):
275 self.update_prompt()
Philipp Maier38c74f62021-03-17 17:19:52 +0100276
Harald Weltec91085e2022-02-10 18:05:45 +0100277 def _onchange_conserve_write(self, param_name, old, new):
278 if self.rs:
279 self.rs.conserve_write = new
Harald Welte7829d8a2021-04-10 11:28:53 +0200280
Harald Weltec91085e2022-02-10 18:05:45 +0100281 def _onchange_apdu_trace(self, param_name, old, new):
282 if self.card:
283 if new == True:
284 self.card._scc._tp.apdu_tracer = self.Cmd2ApduTracer(self)
285 else:
286 self.card._scc._tp.apdu_tracer = None
Harald Welte7829d8a2021-04-10 11:28:53 +0200287
Harald Weltec91085e2022-02-10 18:05:45 +0100288 class Cmd2ApduTracer(ApduTracer):
289 def __init__(self, cmd2_app):
Philipp Maier91c971b2023-10-09 10:48:46 +0200290 self.cmd2 = cmd2_app
Harald Welte7829d8a2021-04-10 11:28:53 +0200291
Harald Weltec91085e2022-02-10 18:05:45 +0100292 def trace_response(self, cmd, sw, resp):
293 self.cmd2.poutput("-> %s %s" % (cmd[:10], cmd[10:]))
294 self.cmd2.poutput("<- %s: %s" % (sw, resp))
Harald Welteb2edd142021-01-08 23:29:35 +0100295
Harald Weltec91085e2022-02-10 18:05:45 +0100296 def update_prompt(self):
Harald Weltea6c0f882022-07-17 14:23:17 +0200297 if self.lchan:
Harald Welteb2e4b4a2022-07-19 23:48:45 +0200298 path_str = self.lchan.selected_file.fully_qualified_path_str(not self.numeric_path)
Harald Welte237ddb52023-10-22 10:36:58 +0200299 self.prompt = 'pySIM-shell (%02u:%s)> ' % (self.lchan.lchan_nr, path_str)
Harald Weltec91085e2022-02-10 18:05:45 +0100300 else:
Philipp Maier7226c092022-06-01 17:58:38 +0200301 if self.card:
302 self.prompt = 'pySIM-shell (no card profile)> '
303 else:
304 self.prompt = 'pySIM-shell (no card)> '
Harald Welteb2edd142021-01-08 23:29:35 +0100305
Harald Weltec91085e2022-02-10 18:05:45 +0100306 @cmd2.with_category(CUSTOM_CATEGORY)
307 def do_intro(self, _):
308 """Display the intro banner"""
309 self.poutput(self.intro)
Philipp Maier9764de22021-11-03 10:44:39 +0100310
Harald Weltec91085e2022-02-10 18:05:45 +0100311 def do_eof(self, _: argparse.Namespace) -> bool:
312 self.poutput("")
313 return self.do_quit('')
Philipp Maier5d698e52021-09-16 13:18:01 +0200314
Harald Weltec91085e2022-02-10 18:05:45 +0100315 @cmd2.with_category(CUSTOM_CATEGORY)
316 def do_equip(self, opts):
317 """Equip pySim-shell with card"""
Philipp Maiere6cba762023-08-11 11:23:17 +0200318 if self.rs and self.rs.profile:
Harald Welte659781c2023-06-06 17:00:51 +0200319 for cmd_set in self.rs.profile.shell_cmdsets:
320 self.unregister_command_set(cmd_set)
Philipp Maier91c971b2023-10-09 10:48:46 +0200321 rs, card = init_card(self.sl)
Harald Weltec91085e2022-02-10 18:05:45 +0100322 self.equip(card, rs)
Philipp Maier76667642021-09-22 16:53:22 +0200323
Philipp Maier7226c092022-06-01 17:58:38 +0200324 apdu_cmd_parser = argparse.ArgumentParser()
Harald Welte4e59d892023-11-01 23:40:07 +0100325 apdu_cmd_parser.add_argument('APDU', type=is_hexstr, help='APDU as hex string')
Philipp Maiere7d1b672022-06-01 18:05:34 +0200326 apdu_cmd_parser.add_argument('--expect-sw', help='expect a specified status word', type=str, default=None)
Philipp Maier7226c092022-06-01 17:58:38 +0200327
328 @cmd2.with_argparser(apdu_cmd_parser)
329 def do_apdu(self, opts):
330 """Send a raw APDU to the card, and print SW + Response.
331 DANGEROUS: pySim-shell will not know any card state changes, and
332 not continue to work as expected if you e.g. select a different
333 file."""
Harald Welte46255122023-10-21 23:40:42 +0200334 data, sw = self.lchan.scc._tp.send_apdu(opts.APDU)
Philipp Maier7226c092022-06-01 17:58:38 +0200335 if data:
336 self.poutput("SW: %s, RESP: %s" % (sw, data))
337 else:
338 self.poutput("SW: %s" % sw)
Philipp Maiere7d1b672022-06-01 18:05:34 +0200339 if opts.expect_sw:
340 if not sw_match(sw, opts.expect_sw):
341 raise SwMatchError(sw, opts.expect_sw)
Philipp Maier7226c092022-06-01 17:58:38 +0200342
Harald Weltec91085e2022-02-10 18:05:45 +0100343 class InterceptStderr(list):
344 def __init__(self):
345 self._stderr_backup = sys.stderr
Philipp Maier76667642021-09-22 16:53:22 +0200346
Harald Weltec91085e2022-02-10 18:05:45 +0100347 def __enter__(self):
348 self._stringio_stderr = StringIO()
349 sys.stderr = self._stringio_stderr
350 return self
Philipp Maier76667642021-09-22 16:53:22 +0200351
Harald Weltec91085e2022-02-10 18:05:45 +0100352 def __exit__(self, *args):
353 self.stderr = self._stringio_stderr.getvalue().strip()
354 del self._stringio_stderr
355 sys.stderr = self._stderr_backup
Philipp Maier76667642021-09-22 16:53:22 +0200356
Harald Weltec91085e2022-02-10 18:05:45 +0100357 def _show_failure_sign(self):
Harald Welte961b8032023-04-27 17:30:22 +0200358 self.poutput(style(" +-------------+", fg=LIGHT_RED))
359 self.poutput(style(" + ## ## +", fg=LIGHT_RED))
360 self.poutput(style(" + ## ## +", fg=LIGHT_RED))
361 self.poutput(style(" + ### +", fg=LIGHT_RED))
362 self.poutput(style(" + ## ## +", fg=LIGHT_RED))
363 self.poutput(style(" + ## ## +", fg=LIGHT_RED))
364 self.poutput(style(" +-------------+", fg=LIGHT_RED))
Harald Weltec91085e2022-02-10 18:05:45 +0100365 self.poutput("")
Philipp Maier76667642021-09-22 16:53:22 +0200366
Harald Weltec91085e2022-02-10 18:05:45 +0100367 def _show_success_sign(self):
Harald Welte961b8032023-04-27 17:30:22 +0200368 self.poutput(style(" +-------------+", fg=LIGHT_GREEN))
369 self.poutput(style(" + ## +", fg=LIGHT_GREEN))
370 self.poutput(style(" + ## +", fg=LIGHT_GREEN))
371 self.poutput(style(" + # ## +", fg=LIGHT_GREEN))
372 self.poutput(style(" + ## # +", fg=LIGHT_GREEN))
373 self.poutput(style(" + ## +", fg=LIGHT_GREEN))
374 self.poutput(style(" +-------------+", fg=LIGHT_GREEN))
Harald Weltec91085e2022-02-10 18:05:45 +0100375 self.poutput("")
Philipp Maier76667642021-09-22 16:53:22 +0200376
Harald Weltec91085e2022-02-10 18:05:45 +0100377 def _process_card(self, first, script_path):
Philipp Maier76667642021-09-22 16:53:22 +0200378
Harald Weltec91085e2022-02-10 18:05:45 +0100379 # Early phase of card initialzation (this part may fail with an exception)
380 try:
381 rs, card = init_card(self.sl)
382 rc = self.equip(card, rs)
383 except:
384 self.poutput("")
Philipp Maier6bfa8a82023-10-09 13:32:49 +0200385 self.poutput("Card initialization (%s) failed with an exception:" % str(self.sl))
Harald Weltec91085e2022-02-10 18:05:45 +0100386 self.poutput("---------------------8<---------------------")
387 traceback.print_exc()
388 self.poutput("---------------------8<---------------------")
389 self.poutput("")
390 return -1
Philipp Maier76667642021-09-22 16:53:22 +0200391
Harald Weltec91085e2022-02-10 18:05:45 +0100392 # Actual card processing step. This part should never fail with an exception since the cmd2
393 # do_run_script method will catch any exception that might occur during script execution.
394 if rc:
395 self.poutput("")
396 self.poutput("Transcript stdout:")
397 self.poutput("---------------------8<---------------------")
398 with self.InterceptStderr() as logged:
399 self.do_run_script(script_path)
400 self.poutput("---------------------8<---------------------")
Philipp Maier76667642021-09-22 16:53:22 +0200401
Harald Weltec91085e2022-02-10 18:05:45 +0100402 self.poutput("")
403 self.poutput("Transcript stderr:")
404 if logged.stderr:
405 self.poutput("---------------------8<---------------------")
406 self.poutput(logged.stderr)
407 self.poutput("---------------------8<---------------------")
408 else:
409 self.poutput("(none)")
Philipp Maier76667642021-09-22 16:53:22 +0200410
Harald Weltec91085e2022-02-10 18:05:45 +0100411 # Check for exceptions
412 self.poutput("")
413 if "EXCEPTION of type" not in logged.stderr:
414 return 0
Philipp Maier76667642021-09-22 16:53:22 +0200415
Harald Weltec91085e2022-02-10 18:05:45 +0100416 return -1
Philipp Maier76667642021-09-22 16:53:22 +0200417
Harald Weltec91085e2022-02-10 18:05:45 +0100418 bulk_script_parser = argparse.ArgumentParser()
419 bulk_script_parser.add_argument(
420 'script_path', help="path to the script file")
421 bulk_script_parser.add_argument('--halt_on_error', help='stop card handling if an exeption occurs',
422 action='store_true')
423 bulk_script_parser.add_argument('--tries', type=int, default=2,
424 help='how many tries before trying the next card')
425 bulk_script_parser.add_argument('--on_stop_action', type=str, default=None,
426 help='commandline to execute when card handling has stopped')
427 bulk_script_parser.add_argument('--pre_card_action', type=str, default=None,
428 help='commandline to execute before actually talking to the card')
Philipp Maier76667642021-09-22 16:53:22 +0200429
Harald Weltec91085e2022-02-10 18:05:45 +0100430 @cmd2.with_argparser(bulk_script_parser)
431 @cmd2.with_category(CUSTOM_CATEGORY)
432 def do_bulk_script(self, opts):
433 """Run script on multiple cards (bulk provisioning)"""
Philipp Maier76667642021-09-22 16:53:22 +0200434
Harald Weltec91085e2022-02-10 18:05:45 +0100435 # Make sure that the script file exists and that it is readable.
436 if not os.access(opts.script_path, os.R_OK):
437 self.poutput("Invalid script file!")
438 return
Philipp Maier76667642021-09-22 16:53:22 +0200439
Harald Weltec91085e2022-02-10 18:05:45 +0100440 success_count = 0
441 fail_count = 0
Philipp Maier76667642021-09-22 16:53:22 +0200442
Harald Weltec91085e2022-02-10 18:05:45 +0100443 first = True
444 while 1:
445 # TODO: Count consecutive failures, if more than N consecutive failures occur, then stop.
446 # The ratinale is: There may be a problem with the device, we do want to prevent that
447 # all remaining cards are fired to the error bin. This is only relevant for situations
448 # with large stacks, probably we do not need this feature right now.
Philipp Maier76667642021-09-22 16:53:22 +0200449
Harald Weltec91085e2022-02-10 18:05:45 +0100450 try:
451 # In case of failure, try multiple times.
452 for i in range(opts.tries):
453 # fetch card into reader bay
Philipp Maier91c971b2023-10-09 10:48:46 +0200454 self.ch.get(first)
Philipp Maier76667642021-09-22 16:53:22 +0200455
Harald Weltec91085e2022-02-10 18:05:45 +0100456 # if necessary execute an action before we start processing the card
457 if(opts.pre_card_action):
458 os.system(opts.pre_card_action)
Philipp Maier76667642021-09-22 16:53:22 +0200459
Harald Weltec91085e2022-02-10 18:05:45 +0100460 # process the card
461 rc = self._process_card(first, opts.script_path)
462 if rc == 0:
463 success_count = success_count + 1
464 self._show_success_sign()
465 self.poutput("Statistics: success :%i, failure: %i" % (
466 success_count, fail_count))
467 break
468 else:
469 fail_count = fail_count + 1
470 self._show_failure_sign()
471 self.poutput("Statistics: success :%i, failure: %i" % (
472 success_count, fail_count))
Philipp Maier76667642021-09-22 16:53:22 +0200473
Harald Weltec91085e2022-02-10 18:05:45 +0100474 # Depending on success or failure, the card goes either in the "error" bin or in the
475 # "done" bin.
476 if rc < 0:
Philipp Maier91c971b2023-10-09 10:48:46 +0200477 self.ch.error()
Harald Weltec91085e2022-02-10 18:05:45 +0100478 else:
Philipp Maier91c971b2023-10-09 10:48:46 +0200479 self.ch.done()
Philipp Maier76667642021-09-22 16:53:22 +0200480
Harald Weltec91085e2022-02-10 18:05:45 +0100481 # In most cases it is possible to proceed with the next card, but the
482 # user may decide to halt immediately when an error occurs
483 if opts.halt_on_error and rc < 0:
484 return
Philipp Maier76667642021-09-22 16:53:22 +0200485
Harald Weltec91085e2022-02-10 18:05:45 +0100486 except (KeyboardInterrupt):
487 self.poutput("")
488 self.poutput("Terminated by user!")
489 return
490 except (SystemExit):
491 # When all cards are processed the card handler device will throw a SystemExit
492 # exception. Also Errors that are not recoverable (cards stuck etc.) will end up here.
493 # The user has the option to execute some action to make aware that the card handler
494 # needs service.
495 if(opts.on_stop_action):
496 os.system(opts.on_stop_action)
497 return
498 except:
499 self.poutput("")
Philipp Maier6bfa8a82023-10-09 13:32:49 +0200500 self.poutput("Card handling (%s) failed with an exception:" % str(self.sl))
Harald Weltec91085e2022-02-10 18:05:45 +0100501 self.poutput("---------------------8<---------------------")
502 traceback.print_exc()
503 self.poutput("---------------------8<---------------------")
504 self.poutput("")
505 fail_count = fail_count + 1
506 self._show_failure_sign()
507 self.poutput("Statistics: success :%i, failure: %i" %
508 (success_count, fail_count))
Philipp Maierb52feed2021-09-22 16:43:13 +0200509
Harald Weltec91085e2022-02-10 18:05:45 +0100510 first = False
511
512 echo_parser = argparse.ArgumentParser()
Harald Welte977c5922023-11-01 23:42:55 +0100513 echo_parser.add_argument('string', help="string to echo on the shell", nargs='+')
Harald Weltec91085e2022-02-10 18:05:45 +0100514
515 @cmd2.with_argparser(echo_parser)
516 @cmd2.with_category(CUSTOM_CATEGORY)
517 def do_echo(self, opts):
518 """Echo (print) a string on the console"""
Harald Welte977c5922023-11-01 23:42:55 +0100519 self.poutput(' '.join(opts.string))
Harald Weltec91085e2022-02-10 18:05:45 +0100520
Harald Weltefc315482022-07-23 12:49:14 +0200521 @cmd2.with_category(CUSTOM_CATEGORY)
522 def do_version(self, opts):
523 """Print the pySim software version."""
524 import pkg_resources
525 self.poutput(pkg_resources.get_distribution('pySim'))
Harald Welteb2edd142021-01-08 23:29:35 +0100526
Harald Welte31d2cf02021-04-03 10:47:29 +0200527@with_default_category('pySim Commands')
528class PySimCommands(CommandSet):
Harald Weltec91085e2022-02-10 18:05:45 +0100529 def __init__(self):
530 super().__init__()
Harald Welteb2edd142021-01-08 23:29:35 +0100531
Harald Weltec91085e2022-02-10 18:05:45 +0100532 dir_parser = argparse.ArgumentParser()
533 dir_parser.add_argument(
534 '--fids', help='Show file identifiers', action='store_true')
535 dir_parser.add_argument(
536 '--names', help='Show file names', action='store_true')
537 dir_parser.add_argument(
538 '--apps', help='Show applications', action='store_true')
539 dir_parser.add_argument(
540 '--all', help='Show all selectable identifiers and names', action='store_true')
Philipp Maier5d3e2592021-02-22 17:22:16 +0100541
Harald Weltec91085e2022-02-10 18:05:45 +0100542 @cmd2.with_argparser(dir_parser)
543 def do_dir(self, opts):
544 """Show a listing of files available in currently selected DF or MF"""
545 if opts.all:
546 flags = []
547 elif opts.fids or opts.names or opts.apps:
548 flags = ['PARENT', 'SELF']
549 if opts.fids:
550 flags += ['FIDS', 'AIDS']
551 if opts.names:
552 flags += ['FNAMES', 'ANAMES']
553 if opts.apps:
554 flags += ['ANAMES', 'AIDS']
555 else:
556 flags = ['PARENT', 'SELF', 'FNAMES', 'ANAMES']
557 selectables = list(
Harald Weltea6c0f882022-07-17 14:23:17 +0200558 self._cmd.lchan.selected_file.get_selectable_names(flags=flags))
Harald Weltec91085e2022-02-10 18:05:45 +0100559 directory_str = tabulate_str_list(
560 selectables, width=79, hspace=2, lspace=1, align_left=True)
Harald Welteb2e4b4a2022-07-19 23:48:45 +0200561 path = self._cmd.lchan.selected_file.fully_qualified_path_str(True)
562 self._cmd.poutput(path)
563 path = self._cmd.lchan.selected_file.fully_qualified_path_str(False)
564 self._cmd.poutput(path)
Harald Weltec91085e2022-02-10 18:05:45 +0100565 self._cmd.poutput(directory_str)
566 self._cmd.poutput("%d files" % len(selectables))
Harald Welteb2edd142021-01-08 23:29:35 +0100567
Philipp Maier7b138b02022-05-31 13:42:56 +0200568 def walk(self, indent=0, action_ef=None, action_df=None, context=None, **kwargs):
Harald Weltec91085e2022-02-10 18:05:45 +0100569 """Recursively walk through the file system, starting at the currently selected DF"""
Philipp Maier7b138b02022-05-31 13:42:56 +0200570
Harald Weltea6c0f882022-07-17 14:23:17 +0200571 if isinstance(self._cmd.lchan.selected_file, CardDF):
Philipp Maier7b138b02022-05-31 13:42:56 +0200572 if action_df:
Philipp Maier91c971b2023-10-09 10:48:46 +0200573 action_df(context, **kwargs)
Philipp Maier7b138b02022-05-31 13:42:56 +0200574
Harald Weltea6c0f882022-07-17 14:23:17 +0200575 files = self._cmd.lchan.selected_file.get_selectables(
Harald Weltec91085e2022-02-10 18:05:45 +0100576 flags=['FNAMES', 'ANAMES'])
577 for f in files:
Philipp Maier7b138b02022-05-31 13:42:56 +0200578 # special case: When no action is performed, just output a directory
579 if not action_ef and not action_df:
Harald Weltec91085e2022-02-10 18:05:45 +0100580 output_str = " " * indent + str(f) + (" " * 250)
581 output_str = output_str[0:25]
582 if isinstance(files[f], CardADF):
583 output_str += " " + str(files[f].aid)
584 else:
585 output_str += " " + str(files[f].fid)
586 output_str += " " + str(files[f].desc)
587 self._cmd.poutput(output_str)
Philipp Maierf408a402021-04-09 21:16:12 +0200588
Harald Weltec91085e2022-02-10 18:05:45 +0100589 if isinstance(files[f], CardDF):
590 skip_df = False
591 try:
Harald Weltea6c0f882022-07-17 14:23:17 +0200592 fcp_dec = self._cmd.lchan.select(f, self._cmd)
Harald Weltec91085e2022-02-10 18:05:45 +0100593 except Exception as e:
594 skip_df = True
Harald Weltea6c0f882022-07-17 14:23:17 +0200595 df = self._cmd.lchan.selected_file
Harald Welteb2e4b4a2022-07-19 23:48:45 +0200596 df_path = df.fully_qualified_path_str(True)
597 df_skip_reason_str = df_path + \
Harald Weltec91085e2022-02-10 18:05:45 +0100598 "/" + str(f) + ", " + str(e)
599 if context:
600 context['DF_SKIP'] += 1
601 context['DF_SKIP_REASON'].append(df_skip_reason_str)
Philipp Maierf408a402021-04-09 21:16:12 +0200602
Harald Weltec91085e2022-02-10 18:05:45 +0100603 # If the DF was skipped, we never have entered the directory
604 # below, so we must not move up.
605 if skip_df == False:
Philipp Maier7b138b02022-05-31 13:42:56 +0200606 self.walk(indent + 1, action_ef, action_df, context, **kwargs)
Harald Weltea6c0f882022-07-17 14:23:17 +0200607 fcp_dec = self._cmd.lchan.select("..", self._cmd)
Philipp Maierf408a402021-04-09 21:16:12 +0200608
Philipp Maier7b138b02022-05-31 13:42:56 +0200609 elif action_ef:
Harald Weltea6c0f882022-07-17 14:23:17 +0200610 df_before_action = self._cmd.lchan.selected_file
Philipp Maier7b138b02022-05-31 13:42:56 +0200611 action_ef(f, context, **kwargs)
Harald Weltec91085e2022-02-10 18:05:45 +0100612 # When walking through the file system tree the action must not
613 # always restore the currently selected file to the file that
614 # was selected before executing the action() callback.
Harald Weltea6c0f882022-07-17 14:23:17 +0200615 if df_before_action != self._cmd.lchan.selected_file:
Harald Weltec91085e2022-02-10 18:05:45 +0100616 raise RuntimeError("inconsistent walk, %s is currently selected but expecting %s to be selected"
Harald Weltea6c0f882022-07-17 14:23:17 +0200617 % (str(self._cmd.lchan.selected_file), str(df_before_action)))
Philipp Maierff9dae22021-02-25 17:03:21 +0100618
Harald Weltec91085e2022-02-10 18:05:45 +0100619 def do_tree(self, opts):
620 """Display a filesystem-tree with all selectable files"""
621 self.walk()
Philipp Maierff9dae22021-02-25 17:03:21 +0100622
Philipp Maier7b138b02022-05-31 13:42:56 +0200623 def export_ef(self, filename, context, as_json):
624 """ Select and export a single elementary file (EF) """
Harald Weltec91085e2022-02-10 18:05:45 +0100625 context['COUNT'] += 1
Harald Weltea6c0f882022-07-17 14:23:17 +0200626 df = self._cmd.lchan.selected_file
Philipp Maierac34dcc2021-04-01 17:19:05 +0200627
Philipp Maierea81f752022-05-19 10:13:30 +0200628 # The currently selected file (not the file we are going to export)
629 # must always be an ADF or DF. From this starting point we select
630 # the EF we want to export. To maintain consistency we will then
631 # select the current DF again (see comment below).
Harald Weltec91085e2022-02-10 18:05:45 +0100632 if not isinstance(df, CardDF):
633 raise RuntimeError(
634 "currently selected file %s is not a DF or ADF" % str(df))
Philipp Maierac34dcc2021-04-01 17:19:05 +0200635
Harald Weltec91085e2022-02-10 18:05:45 +0100636 df_path_list = df.fully_qualified_path(True)
Harald Welteb2e4b4a2022-07-19 23:48:45 +0200637 df_path = df.fully_qualified_path_str(True)
638 df_path_fid = df.fully_qualified_path_str(False)
Philipp Maier24f7bd32021-02-25 17:06:18 +0100639
Harald Welteb2e4b4a2022-07-19 23:48:45 +0200640 file_str = df_path + "/" + str(filename)
Harald Weltec91085e2022-02-10 18:05:45 +0100641 self._cmd.poutput(boxed_heading_str(file_str))
Philipp Maier24f7bd32021-02-25 17:06:18 +0100642
Harald Welteb2e4b4a2022-07-19 23:48:45 +0200643 self._cmd.poutput("# directory: %s (%s)" % (df_path, df_path_fid))
Harald Weltec91085e2022-02-10 18:05:45 +0100644 try:
Harald Weltea6c0f882022-07-17 14:23:17 +0200645 fcp_dec = self._cmd.lchan.select(filename, self._cmd)
Harald Weltec91085e2022-02-10 18:05:45 +0100646 self._cmd.poutput("# file: %s (%s)" % (
Harald Weltea6c0f882022-07-17 14:23:17 +0200647 self._cmd.lchan.selected_file.name, self._cmd.lchan.selected_file.fid))
Philipp Maier24f7bd32021-02-25 17:06:18 +0100648
Harald Weltea6c0f882022-07-17 14:23:17 +0200649 structure = self._cmd.lchan.selected_file_structure()
Harald Weltec91085e2022-02-10 18:05:45 +0100650 self._cmd.poutput("# structure: %s" % str(structure))
Harald Weltea6c0f882022-07-17 14:23:17 +0200651 self._cmd.poutput("# RAW FCP Template: %s" % str(self._cmd.lchan.selected_file_fcp_hex))
652 self._cmd.poutput("# Decoded FCP Template: %s" % str(self._cmd.lchan.selected_file_fcp))
Philipp Maier24f7bd32021-02-25 17:06:18 +0100653
Harald Weltec91085e2022-02-10 18:05:45 +0100654 for f in df_path_list:
655 self._cmd.poutput("select " + str(f))
Harald Weltea6c0f882022-07-17 14:23:17 +0200656 self._cmd.poutput("select " + self._cmd.lchan.selected_file.name)
Philipp Maier24f7bd32021-02-25 17:06:18 +0100657
Harald Weltec91085e2022-02-10 18:05:45 +0100658 if structure == 'transparent':
Harald Welte08b11ab2022-02-10 18:56:41 +0100659 if as_json:
Harald Weltea6c0f882022-07-17 14:23:17 +0200660 result = self._cmd.lchan.read_binary_dec()
Harald Welte08b11ab2022-02-10 18:56:41 +0100661 self._cmd.poutput("update_binary_decoded '%s'" % json.dumps(result[0], cls=JsonEncoder))
662 else:
Harald Weltea6c0f882022-07-17 14:23:17 +0200663 result = self._cmd.lchan.read_binary()
Harald Welte08b11ab2022-02-10 18:56:41 +0100664 self._cmd.poutput("update_binary " + str(result[0]))
Harald Weltec91085e2022-02-10 18:05:45 +0100665 elif structure == 'cyclic' or structure == 'linear_fixed':
666 # Use number of records specified in select response
Harald Weltea6c0f882022-07-17 14:23:17 +0200667 num_of_rec = self._cmd.lchan.selected_file_num_of_rec()
Harald Welte747a9782022-02-13 17:52:28 +0100668 if num_of_rec:
Harald Weltec91085e2022-02-10 18:05:45 +0100669 for r in range(1, num_of_rec + 1):
Harald Welte08b11ab2022-02-10 18:56:41 +0100670 if as_json:
Harald Weltea6c0f882022-07-17 14:23:17 +0200671 result = self._cmd.lchan.read_record_dec(r)
Harald Welte08b11ab2022-02-10 18:56:41 +0100672 self._cmd.poutput("update_record_decoded %d '%s'" % (r, json.dumps(result[0], cls=JsonEncoder)))
673 else:
Harald Weltea6c0f882022-07-17 14:23:17 +0200674 result = self._cmd.lchan.read_record(r)
Harald Welte08b11ab2022-02-10 18:56:41 +0100675 self._cmd.poutput("update_record %d %s" % (r, str(result[0])))
676
Harald Weltec91085e2022-02-10 18:05:45 +0100677 # When the select response does not return the number of records, read until we hit the
678 # first record that cannot be read.
679 else:
680 r = 1
681 while True:
682 try:
Harald Welte08b11ab2022-02-10 18:56:41 +0100683 if as_json:
Harald Weltea6c0f882022-07-17 14:23:17 +0200684 result = self._cmd.lchan.read_record_dec(r)
Harald Welte08b11ab2022-02-10 18:56:41 +0100685 self._cmd.poutput("update_record_decoded %d '%s'" % (r, json.dumps(result[0], cls=JsonEncoder)))
686 else:
Harald Weltea6c0f882022-07-17 14:23:17 +0200687 result = self._cmd.lchan.read_record(r)
Harald Welte08b11ab2022-02-10 18:56:41 +0100688 self._cmd.poutput("update_record %d %s" % (r, str(result[0])))
Harald Weltec91085e2022-02-10 18:05:45 +0100689 except SwMatchError as e:
690 # We are past the last valid record - stop
691 if e.sw_actual == "9402":
692 break
693 # Some other problem occurred
694 else:
695 raise e
Harald Weltec91085e2022-02-10 18:05:45 +0100696 r = r + 1
697 elif structure == 'ber_tlv':
Harald Weltea6c0f882022-07-17 14:23:17 +0200698 tags = self._cmd.lchan.retrieve_tags()
Harald Weltec91085e2022-02-10 18:05:45 +0100699 for t in tags:
Harald Weltea6c0f882022-07-17 14:23:17 +0200700 result = self._cmd.lchan.retrieve_data(t)
Harald Weltec91085e2022-02-10 18:05:45 +0100701 (tag, l, val, remainer) = bertlv_parse_one(h2b(result[0]))
702 self._cmd.poutput("set_data 0x%02x %s" % (t, b2h(val)))
703 else:
704 raise RuntimeError(
705 'Unsupported structure "%s" of file "%s"' % (structure, filename))
706 except Exception as e:
Harald Welteb2e4b4a2022-07-19 23:48:45 +0200707 bad_file_str = df_path + "/" + str(filename) + ", " + str(e)
Harald Weltec91085e2022-02-10 18:05:45 +0100708 self._cmd.poutput("# bad file: %s" % bad_file_str)
709 context['ERR'] += 1
710 context['BAD'].append(bad_file_str)
Philipp Maier24f7bd32021-02-25 17:06:18 +0100711
Harald Weltec91085e2022-02-10 18:05:45 +0100712 # When reading the file is done, make sure the parent file is
713 # selected again. This will be the usual case, however we need
714 # to check before since we must not select the same DF twice
Harald Weltea6c0f882022-07-17 14:23:17 +0200715 if df != self._cmd.lchan.selected_file:
716 self._cmd.lchan.select(df.fid or df.aid, self._cmd)
Philipp Maierac34dcc2021-04-01 17:19:05 +0200717
Harald Weltec91085e2022-02-10 18:05:45 +0100718 self._cmd.poutput("#")
Philipp Maier24f7bd32021-02-25 17:06:18 +0100719
Harald Weltec91085e2022-02-10 18:05:45 +0100720 export_parser = argparse.ArgumentParser()
721 export_parser.add_argument(
722 '--filename', type=str, default=None, help='only export specific file')
Harald Welte08b11ab2022-02-10 18:56:41 +0100723 export_parser.add_argument(
724 '--json', action='store_true', help='export as JSON (less reliable)')
Philipp Maier24f7bd32021-02-25 17:06:18 +0100725
Harald Weltec91085e2022-02-10 18:05:45 +0100726 @cmd2.with_argparser(export_parser)
727 def do_export(self, opts):
728 """Export files to script that can be imported back later"""
729 context = {'ERR': 0, 'COUNT': 0, 'BAD': [],
730 'DF_SKIP': 0, 'DF_SKIP_REASON': []}
Philipp Maier9a4091d2022-05-19 10:20:30 +0200731 kwargs_export = {'as_json': opts.json}
Philipp Maierf16ac6a2022-05-31 14:08:47 +0200732 exception_str_add = ""
733
Harald Weltec91085e2022-02-10 18:05:45 +0100734 if opts.filename:
Philipp Maier7b138b02022-05-31 13:42:56 +0200735 self.export_ef(opts.filename, context, **kwargs_export)
Harald Weltec91085e2022-02-10 18:05:45 +0100736 else:
Philipp Maierf16ac6a2022-05-31 14:08:47 +0200737 try:
738 self.walk(0, self.export_ef, None, context, **kwargs_export)
739 except Exception as e:
740 print("# Stopping early here due to exception: " + str(e))
741 print("#")
742 exception_str_add = ", also had to stop early due to exception:" + str(e)
Philipp Maier80ce71f2021-04-19 21:24:23 +0200743
Harald Weltec91085e2022-02-10 18:05:45 +0100744 self._cmd.poutput(boxed_heading_str("Export summary"))
Philipp Maier80ce71f2021-04-19 21:24:23 +0200745
Harald Weltec91085e2022-02-10 18:05:45 +0100746 self._cmd.poutput("# total files visited: %u" % context['COUNT'])
747 self._cmd.poutput("# bad files: %u" % context['ERR'])
748 for b in context['BAD']:
749 self._cmd.poutput("# " + b)
Philipp Maierf408a402021-04-09 21:16:12 +0200750
Harald Weltec91085e2022-02-10 18:05:45 +0100751 self._cmd.poutput("# skipped dedicated files(s): %u" %
752 context['DF_SKIP'])
753 for b in context['DF_SKIP_REASON']:
754 self._cmd.poutput("# " + b)
Philipp Maierf408a402021-04-09 21:16:12 +0200755
Harald Weltec91085e2022-02-10 18:05:45 +0100756 if context['ERR'] and context['DF_SKIP']:
Philipp Maierf16ac6a2022-05-31 14:08:47 +0200757 raise RuntimeError("unable to export %i elementary file(s) and %i dedicated file(s)%s" % (
758 context['ERR'], context['DF_SKIP'], exception_str_add))
Harald Weltec91085e2022-02-10 18:05:45 +0100759 elif context['ERR']:
760 raise RuntimeError(
Philipp Maierf16ac6a2022-05-31 14:08:47 +0200761 "unable to export %i elementary file(s)%s" % (context['ERR'], exception_str_add))
Harald Weltec91085e2022-02-10 18:05:45 +0100762 elif context['DF_SKIP']:
763 raise RuntimeError(
Philipp Maierf16ac6a2022-05-31 14:08:47 +0200764 "unable to export %i dedicated files(s)%s" % (context['ERR'], exception_str_add))
Harald Welteb2edd142021-01-08 23:29:35 +0100765
Harald Weltec91085e2022-02-10 18:05:45 +0100766 def do_reset(self, opts):
767 """Reset the Card."""
Philipp Maiere345e112023-06-09 15:19:56 +0200768 atr = self._cmd.card.reset()
769 self._cmd.poutput('Card ATR: %s' % i2h(atr))
Harald Weltec91085e2022-02-10 18:05:45 +0100770 self._cmd.update_prompt()
Harald Weltedaf2b392021-05-03 23:17:29 +0200771
Harald Weltec91085e2022-02-10 18:05:45 +0100772 def do_desc(self, opts):
773 """Display human readable file description for the currently selected file"""
Harald Weltea6c0f882022-07-17 14:23:17 +0200774 desc = self._cmd.lchan.selected_file.desc
Harald Weltec91085e2022-02-10 18:05:45 +0100775 if desc:
776 self._cmd.poutput(desc)
777 else:
778 self._cmd.poutput("no description available")
Philipp Maiera8c9ea92021-09-16 12:51:46 +0200779
Harald Welte469db932023-11-01 23:17:06 +0100780 verify_adm_parser = argparse.ArgumentParser()
Harald Weltef9ea63e2023-11-01 23:35:31 +0100781 verify_adm_parser.add_argument('ADM1', nargs='?', type=is_hexstr_or_decimal,
Harald Welte469db932023-11-01 23:17:06 +0100782 help='ADM1 pin value. If none given, CSV file will be queried')
783
784 @cmd2.with_argparser(verify_adm_parser)
785 def do_verify_adm(self, opts):
786 """Verify the ADM (Administrator) PIN specified as argument. This is typically needed in order
787to get write/update permissions to most of the files on SIM cards.
788
789Currently only ADM1 is supported."""
790 if opts.ADM1:
Harald Weltec91085e2022-02-10 18:05:45 +0100791 # use specified ADM-PIN
Harald Welte469db932023-11-01 23:17:06 +0100792 pin_adm = sanitize_pin_adm(opts.ADM1)
Harald Weltec91085e2022-02-10 18:05:45 +0100793 else:
794 # try to find an ADM-PIN if none is specified
795 result = card_key_provider_get_field(
796 'ADM1', key='ICCID', value=self._cmd.iccid)
797 pin_adm = sanitize_pin_adm(result)
798 if pin_adm:
799 self._cmd.poutput(
800 "found ADM-PIN '%s' for ICCID '%s'" % (result, self._cmd.iccid))
801 else:
802 raise ValueError(
803 "cannot find ADM-PIN for ICCID '%s'" % (self._cmd.iccid))
Philipp Maiera8c9ea92021-09-16 12:51:46 +0200804
Harald Weltec91085e2022-02-10 18:05:45 +0100805 if pin_adm:
Harald Welte46255122023-10-21 23:40:42 +0200806 self._cmd.lchan.scc.verify_chv(self._cmd.card._adm_chv_num, h2b(pin_adm))
Harald Weltec91085e2022-02-10 18:05:45 +0100807 else:
808 raise ValueError("error: cannot authenticate, no adm-pin!")
809
Philipp Maier7b9e2442023-03-22 15:19:54 +0100810 def do_cardinfo(self, opts):
811 """Display information about the currently inserted card"""
812 self._cmd.poutput("Card info:")
813 self._cmd.poutput(" Name: %s" % self._cmd.card.name)
Harald Welte46255122023-10-21 23:40:42 +0200814 self._cmd.poutput(" ATR: %s" % b2h(self._cmd.lchan.scc.get_atr()))
Philipp Maier7b9e2442023-03-22 15:19:54 +0100815 self._cmd.poutput(" ICCID: %s" % self._cmd.iccid)
Harald Welte46255122023-10-21 23:40:42 +0200816 self._cmd.poutput(" Class-Byte: %s" % self._cmd.lchan.scc.cla_byte)
817 self._cmd.poutput(" Select-Ctrl: %s" % self._cmd.lchan.scc.sel_ctrl)
Philipp Maier7b9e2442023-03-22 15:19:54 +0100818 self._cmd.poutput(" AIDs:")
819 for a in self._cmd.rs.mf.applications:
820 self._cmd.poutput(" %s" % a)
Harald Welteb2edd142021-01-08 23:29:35 +0100821
Harald Welte31d2cf02021-04-03 10:47:29 +0200822@with_default_category('ISO7816 Commands')
823class Iso7816Commands(CommandSet):
Harald Weltec91085e2022-02-10 18:05:45 +0100824 def __init__(self):
825 super().__init__()
Harald Welte31d2cf02021-04-03 10:47:29 +0200826
Harald Weltec91085e2022-02-10 18:05:45 +0100827 def do_select(self, opts):
828 """SELECT a File (ADF/DF/EF)"""
829 if len(opts.arg_list) == 0:
Harald Welteb2e4b4a2022-07-19 23:48:45 +0200830 path = self._cmd.lchan.selected_file.fully_qualified_path_str(True)
831 path_fid = self._cmd.lchan.selected_file.fully_qualified_path_str(False)
832 self._cmd.poutput("currently selected file: %s (%s)" % (path, path_fid))
Harald Weltec91085e2022-02-10 18:05:45 +0100833 return
Harald Welte31d2cf02021-04-03 10:47:29 +0200834
Harald Weltec91085e2022-02-10 18:05:45 +0100835 path = opts.arg_list[0]
Harald Weltea6c0f882022-07-17 14:23:17 +0200836 fcp_dec = self._cmd.lchan.select(path, self._cmd)
Harald Weltec91085e2022-02-10 18:05:45 +0100837 self._cmd.update_prompt()
838 self._cmd.poutput_json(fcp_dec)
Harald Welte31d2cf02021-04-03 10:47:29 +0200839
Harald Weltec91085e2022-02-10 18:05:45 +0100840 def complete_select(self, text, line, begidx, endidx) -> List[str]:
841 """Command Line tab completion for SELECT"""
Harald Weltea6c0f882022-07-17 14:23:17 +0200842 index_dict = {1: self._cmd.lchan.selected_file.get_selectable_names()}
Harald Weltec91085e2022-02-10 18:05:45 +0100843 return self._cmd.index_based_complete(text, line, begidx, endidx, index_dict=index_dict)
Harald Welte31d2cf02021-04-03 10:47:29 +0200844
Harald Weltec91085e2022-02-10 18:05:45 +0100845 def get_code(self, code):
846 """Use code either directly or try to get it from external data source"""
847 auto = ('PIN1', 'PIN2', 'PUK1', 'PUK2')
Harald Welte31d2cf02021-04-03 10:47:29 +0200848
Harald Weltec91085e2022-02-10 18:05:45 +0100849 if str(code).upper() not in auto:
850 return sanitize_pin_adm(code)
Harald Welte31d2cf02021-04-03 10:47:29 +0200851
Harald Weltec91085e2022-02-10 18:05:45 +0100852 result = card_key_provider_get_field(
853 str(code), key='ICCID', value=self._cmd.iccid)
854 result = sanitize_pin_adm(result)
855 if result:
856 self._cmd.poutput("found %s '%s' for ICCID '%s'" %
857 (code.upper(), result, self._cmd.iccid))
858 else:
859 self._cmd.poutput("cannot find %s for ICCID '%s'" %
860 (code.upper(), self._cmd.iccid))
861 return result
Harald Welte31d2cf02021-04-03 10:47:29 +0200862
Harald Weltec91085e2022-02-10 18:05:45 +0100863 verify_chv_parser = argparse.ArgumentParser()
864 verify_chv_parser.add_argument(
865 '--pin-nr', type=int, default=1, help='PIN Number, 1=PIN1, 2=PIN2 or custom value (decimal)')
866 verify_chv_parser.add_argument(
867 '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 +0200868
Harald Weltec91085e2022-02-10 18:05:45 +0100869 @cmd2.with_argparser(verify_chv_parser)
870 def do_verify_chv(self, opts):
Harald Welte12af7932022-02-15 16:39:08 +0100871 """Verify (authenticate) using specified CHV (PIN) code, which is how the specifications
872 call it if you authenticate yourself using the specified PIN. There usually is at least PIN1 and
873 PIN2."""
Harald Weltec91085e2022-02-10 18:05:45 +0100874 pin = self.get_code(opts.pin_code)
Harald Welte46255122023-10-21 23:40:42 +0200875 (data, sw) = self._cmd.lchan.scc.verify_chv(opts.pin_nr, h2b(pin))
Harald Weltec91085e2022-02-10 18:05:45 +0100876 self._cmd.poutput("CHV verification successful")
Harald Welte31d2cf02021-04-03 10:47:29 +0200877
Harald Weltec91085e2022-02-10 18:05:45 +0100878 unblock_chv_parser = argparse.ArgumentParser()
879 unblock_chv_parser.add_argument(
880 '--pin-nr', type=int, default=1, help='PUK Number, 1=PIN1, 2=PIN2 or custom value (decimal)')
881 unblock_chv_parser.add_argument(
882 'puk_code', type=str, help='PUK code digits \"PUK1\" or \"PUK2\" to get PUK code from external data source')
883 unblock_chv_parser.add_argument(
884 '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 +0200885
Harald Weltec91085e2022-02-10 18:05:45 +0100886 @cmd2.with_argparser(unblock_chv_parser)
887 def do_unblock_chv(self, opts):
888 """Unblock PIN code using specified PUK code"""
889 new_pin = self.get_code(opts.new_pin_code)
890 puk = self.get_code(opts.puk_code)
Harald Welte46255122023-10-21 23:40:42 +0200891 (data, sw) = self._cmd.lchan.scc.unblock_chv(
Harald Weltec91085e2022-02-10 18:05:45 +0100892 opts.pin_nr, h2b(puk), h2b(new_pin))
893 self._cmd.poutput("CHV unblock successful")
Harald Welte31d2cf02021-04-03 10:47:29 +0200894
Harald Weltec91085e2022-02-10 18:05:45 +0100895 change_chv_parser = argparse.ArgumentParser()
896 change_chv_parser.add_argument(
897 '--pin-nr', type=int, default=1, help='PUK Number, 1=PIN1, 2=PIN2 or custom value (decimal)')
898 change_chv_parser.add_argument(
899 'pin_code', type=str, help='PIN code digits \"PIN1\" or \"PIN2\" to get PIN code from external data source')
900 change_chv_parser.add_argument(
901 '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 +0200902
Harald Weltec91085e2022-02-10 18:05:45 +0100903 @cmd2.with_argparser(change_chv_parser)
904 def do_change_chv(self, opts):
905 """Change PIN code to a new PIN code"""
906 new_pin = self.get_code(opts.new_pin_code)
907 pin = self.get_code(opts.pin_code)
Harald Welte46255122023-10-21 23:40:42 +0200908 (data, sw) = self._cmd.lchan.scc.change_chv(
Harald Weltec91085e2022-02-10 18:05:45 +0100909 opts.pin_nr, h2b(pin), h2b(new_pin))
910 self._cmd.poutput("CHV change successful")
Harald Welte31d2cf02021-04-03 10:47:29 +0200911
Harald Weltec91085e2022-02-10 18:05:45 +0100912 disable_chv_parser = argparse.ArgumentParser()
913 disable_chv_parser.add_argument(
914 '--pin-nr', type=int, default=1, help='PIN Number, 1=PIN1, 2=PIN2 or custom value (decimal)')
915 disable_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(disable_chv_parser)
919 def do_disable_chv(self, opts):
920 """Disable 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.disable_chv(opts.pin_nr, h2b(pin))
Harald Weltec91085e2022-02-10 18:05:45 +0100923 self._cmd.poutput("CHV disable successful")
Harald Welte31d2cf02021-04-03 10:47:29 +0200924
Harald Weltec91085e2022-02-10 18:05:45 +0100925 enable_chv_parser = argparse.ArgumentParser()
926 enable_chv_parser.add_argument(
927 '--pin-nr', type=int, default=1, help='PIN Number, 1=PIN1, 2=PIN2 or custom value (decimal)')
928 enable_chv_parser.add_argument(
929 '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 +0200930
Harald Weltec91085e2022-02-10 18:05:45 +0100931 @cmd2.with_argparser(enable_chv_parser)
932 def do_enable_chv(self, opts):
933 """Enable PIN code using specified PIN code"""
934 pin = self.get_code(opts.pin_code)
Harald Welte46255122023-10-21 23:40:42 +0200935 (data, sw) = self._cmd.lchan.scc.enable_chv(opts.pin_nr, h2b(pin))
Harald Weltec91085e2022-02-10 18:05:45 +0100936 self._cmd.poutput("CHV enable successful")
Harald Welte31d2cf02021-04-03 10:47:29 +0200937
Harald Weltec91085e2022-02-10 18:05:45 +0100938 def do_deactivate_file(self, opts):
Harald Welte799c3542022-02-15 15:56:28 +0100939 """Deactivate the currently selected EF"""
Harald Welte46255122023-10-21 23:40:42 +0200940 (data, sw) = self._cmd.lchan.scc.deactivate_file()
Harald Weltea4631612021-04-10 18:17:55 +0200941
Harald Welte799c3542022-02-15 15:56:28 +0100942 activate_file_parser = argparse.ArgumentParser()
943 activate_file_parser.add_argument('NAME', type=str, help='File name or FID of file to activate')
944 @cmd2.with_argparser(activate_file_parser)
Harald Weltec91085e2022-02-10 18:05:45 +0100945 def do_activate_file(self, opts):
Harald Welte12af7932022-02-15 16:39:08 +0100946 """Activate the specified EF. This used to be called REHABILITATE in TS 11.11 for classic
947 SIM. You need to specify the name or FID of the file to activate."""
Harald Weltea6c0f882022-07-17 14:23:17 +0200948 (data, sw) = self._cmd.lchan.activate_file(opts.NAME)
Harald Welte485692b2021-05-25 22:21:44 +0200949
Harald Weltec91085e2022-02-10 18:05:45 +0100950 def complete_activate_file(self, text, line, begidx, endidx) -> List[str]:
951 """Command Line tab completion for ACTIVATE FILE"""
Harald Weltea6c0f882022-07-17 14:23:17 +0200952 index_dict = {1: self._cmd.lchan.selected_file.get_selectable_names()}
Harald Weltec91085e2022-02-10 18:05:45 +0100953 return self._cmd.index_based_complete(text, line, begidx, endidx, index_dict=index_dict)
Harald Welte31d2cf02021-04-03 10:47:29 +0200954
Harald Weltec91085e2022-02-10 18:05:45 +0100955 open_chan_parser = argparse.ArgumentParser()
956 open_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(open_chan_parser)
960 def do_open_channel(self, opts):
961 """Open 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='open', 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.lchan.add_lchan(opts.chan_nr)
Harald Welte703f9332021-04-10 18:39:32 +0200966
Harald Weltec91085e2022-02-10 18:05:45 +0100967 close_chan_parser = argparse.ArgumentParser()
968 close_chan_parser.add_argument(
969 'chan_nr', type=int, default=0, help='Channel Number')
Harald Welte703f9332021-04-10 18:39:32 +0200970
Harald Weltec91085e2022-02-10 18:05:45 +0100971 @cmd2.with_argparser(close_chan_parser)
972 def do_close_channel(self, opts):
973 """Close a logical channel."""
Harald Welte46255122023-10-21 23:40:42 +0200974 (data, sw) = self._cmd.lchan.scc.manage_channel(
Harald Weltec91085e2022-02-10 18:05:45 +0100975 mode='close', lchan_nr=opts.chan_nr)
Harald Weltebdf59572023-10-21 20:06:19 +0200976 # this is executed only in successful case, as unsuccessful raises exception
977 self._cmd.rs.del_lchan(opts.chan_nr)
Harald Welte703f9332021-04-10 18:39:32 +0200978
Harald Welte20650992023-10-21 23:47:02 +0200979 switch_chan_parser = argparse.ArgumentParser()
980 switch_chan_parser.add_argument(
981 'chan_nr', type=int, default=0, help='Channel Number')
982
983 @cmd2.with_argparser(switch_chan_parser)
984 def do_switch_channel(self, opts):
985 """Switch currently active logical channel."""
986 self._cmd.lchan._select_pre(self._cmd)
987 self._cmd.lchan = self._cmd.rs.lchan[opts.chan_nr]
988 self._cmd.lchan._select_post(self._cmd)
989 self._cmd.update_prompt()
990
Harald Weltec91085e2022-02-10 18:05:45 +0100991 def do_status(self, opts):
992 """Perform the STATUS command."""
Harald Weltea6c0f882022-07-17 14:23:17 +0200993 fcp_dec = self._cmd.lchan.status()
Harald Weltec91085e2022-02-10 18:05:45 +0100994 self._cmd.poutput_json(fcp_dec)
Harald Welte34b05d32021-05-25 22:03:13 +0200995
Harald Welteec950532021-10-20 13:09:00 +0200996
Harald Weltecab26c72022-08-06 16:12:30 +0200997class Proact(ProactiveHandler):
998 def receive_fetch(self, pcmd: ProactiveCommand):
999 # print its parsed representation
1000 print(pcmd.decoded)
1001 # TODO: implement the basics, such as SMS Sending, ...
1002
1003
Harald Welte703f9332021-04-10 18:39:32 +02001004
Harald Weltef422eb12023-06-09 11:15:09 +02001005option_parser = argparse.ArgumentParser(description='interactive SIM card shell',
Harald Weltef2e761c2021-04-11 11:56:44 +02001006 formatter_class=argparse.ArgumentDefaultsHelpFormatter)
Harald Welte28c24312021-04-11 12:19:36 +02001007argparse_add_reader_args(option_parser)
Harald Weltec8ff0262021-04-11 12:06:13 +02001008
1009global_group = option_parser.add_argument_group('General Options')
1010global_group.add_argument('--script', metavar='PATH', default=None,
Harald Welte28c24312021-04-11 12:19:36 +02001011 help='script with pySim-shell commands to be executed automatically at start-up')
Harald Weltec91085e2022-02-10 18:05:45 +01001012global_group.add_argument('--csv', metavar='FILE',
1013 default=None, help='Read card data from CSV file')
Philipp Maier76667642021-09-22 16:53:22 +02001014global_group.add_argument("--card_handler", dest="card_handler_config", metavar="FILE",
Harald Weltec91085e2022-02-10 18:05:45 +01001015 help="Use automatic card handling machine")
Harald Weltec8ff0262021-04-11 12:06:13 +02001016
1017adm_group = global_group.add_mutually_exclusive_group()
1018adm_group.add_argument('-a', '--pin-adm', metavar='PIN_ADM1', dest='pin_adm', default=None,
1019 help='ADM PIN used for provisioning (overwrites default)')
1020adm_group.add_argument('-A', '--pin-adm-hex', metavar='PIN_ADM1_HEX', dest='pin_adm_hex', default=None,
1021 help='ADM PIN used for provisioning, as hex string (16 characters long)')
Harald Welteb2edd142021-01-08 23:29:35 +01001022
Harald Welte38306df2023-07-11 21:17:55 +02001023option_parser.add_argument("command", nargs='?',
1024 help="A pySim-shell command that would optionally be executed at startup")
1025option_parser.add_argument('command_args', nargs=argparse.REMAINDER,
1026 help="Optional Arguments for command")
1027
Harald Welteb2edd142021-01-08 23:29:35 +01001028
1029if __name__ == '__main__':
1030
Harald Weltec91085e2022-02-10 18:05:45 +01001031 # Parse options
1032 opts = option_parser.parse_args()
Harald Welteb2edd142021-01-08 23:29:35 +01001033
Harald Weltec91085e2022-02-10 18:05:45 +01001034 # If a script file is specified, be sure that it actually exists
1035 if opts.script:
1036 if not os.access(opts.script, os.R_OK):
1037 print("Invalid script file!")
1038 sys.exit(2)
Philipp Maier13e258d2021-04-08 17:48:49 +02001039
Harald Weltec91085e2022-02-10 18:05:45 +01001040 # Register csv-file as card data provider, either from specified CSV
1041 # or from CSV file in home directory
1042 csv_default = str(Path.home()) + "/.osmocom/pysim/card_data.csv"
1043 if opts.csv:
1044 card_key_provider_register(CardKeyProviderCsv(opts.csv))
1045 if os.path.isfile(csv_default):
1046 card_key_provider_register(CardKeyProviderCsv(csv_default))
Philipp Maier2b11c322021-03-17 12:37:39 +01001047
Harald Weltec91085e2022-02-10 18:05:45 +01001048 # Init card reader driver
Harald Weltecab26c72022-08-06 16:12:30 +02001049 sl = init_reader(opts, proactive_handler = Proact())
Philipp Maierea95c392021-09-16 13:10:19 +02001050
Harald Weltec91085e2022-02-10 18:05:45 +01001051 # Create a card handler (for bulk provisioning)
1052 if opts.card_handler_config:
1053 ch = CardHandlerAuto(None, opts.card_handler_config)
1054 else:
1055 ch = CardHandler(sl)
Philipp Maier76667642021-09-22 16:53:22 +02001056
Harald Weltec91085e2022-02-10 18:05:45 +01001057 # Detect and initialize the card in the reader. This may fail when there
1058 # is no card in the reader or the card is unresponsive. PysimApp is
1059 # able to tolerate and recover from that.
1060 try:
1061 rs, card = init_card(sl)
1062 app = PysimApp(card, rs, sl, ch, opts.script)
1063 except:
Philipp Maier6bfa8a82023-10-09 13:32:49 +02001064 print("Card initialization (%s) failed with an exception:" % str(sl))
Harald Weltec91085e2022-02-10 18:05:45 +01001065 print("---------------------8<---------------------")
1066 traceback.print_exc()
1067 print("---------------------8<---------------------")
1068 print("(you may still try to recover from this manually by using the 'equip' command.)")
1069 print(
1070 " it should also be noted that some readers may behave strangely when no card")
1071 print(" is inserted.)")
1072 print("")
Philipp Maier8e03f2f2023-10-09 13:27:59 +02001073 app = PysimApp(None, None, sl, ch, opts.script)
Philipp Maierea95c392021-09-16 13:10:19 +02001074
Harald Weltec91085e2022-02-10 18:05:45 +01001075 # If the user supplies an ADM PIN at via commandline args authenticate
1076 # immediately so that the user does not have to use the shell commands
1077 pin_adm = sanitize_pin_adm(opts.pin_adm, opts.pin_adm_hex)
1078 if pin_adm:
1079 if not card:
1080 print("Card error, cannot do ADM verification with supplied ADM pin now.")
1081 try:
Philipp Maier4840d4d2023-09-06 14:54:14 +02001082 card._scc.verify_chv(card._adm_chv_num, h2b(pin_adm))
Harald Weltec91085e2022-02-10 18:05:45 +01001083 except Exception as e:
1084 print(e)
Philipp Maier228c98e2021-03-10 20:14:06 +01001085
Harald Welte38306df2023-07-11 21:17:55 +02001086 if opts.command:
1087 app.onecmd_plus_hooks('{} {}'.format(opts.command, ' '.join(opts.command_args)))
1088 else:
1089 app.cmdloop()