1 //-----------------------------------------------------------------------------
2 // Copyright (C) Proxmark3 contributors. See AUTHORS.md for details.
4 // This program is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation, either version 3 of the License, or
7 // (at your option) any later version.
9 // This program is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 // GNU General Public License for more details.
14 // See LICENSE.txt for the text of the license.
15 //-----------------------------------------------------------------------------
16 // Some Lua and Python scripting glue to proxmark core.
17 //-----------------------------------------------------------------------------
23 //#define PY_SSIZE_T_CLEAN
29 #include "cmdparser.h" // command_t
30 #include "scripting.h"
32 #include "cmdscript.h"
34 #include "pm3_binlib.h"
35 #include "pm3_bitlib.h"
38 #include "proxmark3.h"
40 #include "fileutils.h"
41 #include "cliparser.h" // cliparsing
44 extern int luaopen_pm3(lua_State
*L
);
48 #ifdef HAVE_PYTHON_SWIG
49 extern PyObject
*PyInit__pm3(void);
50 #endif // HAVE_PYTHON_SWIG
52 // Partly ripped from PyRun_SimpleFileExFlags
53 // but does not terminate client on sys.exit
54 // and print exit code only if != 0
55 static int Pm3PyRun_SimpleFileNoExit(FILE *fp
, const char *filename
) {
57 int set_file_name
= 0, ret
= -1;
59 m
= PyImport_AddModule("__main__");
65 d
= PyModule_GetDict(m
);
67 if (PyDict_GetItemString(d
, "__file__") == NULL
) {
70 f
= PyUnicode_DecodeFSDefault(filename
);
75 if (PyDict_SetItemString(d
, "__file__", f
) < 0) {
80 if (PyDict_SetItemString(d
, "__cached__", Py_None
) < 0) {
89 v
= PyRun_FileExFlags(fp
, filename
, Py_file_input
, d
, d
, 1, NULL
);
94 if (PyErr_ExceptionMatches(PyExc_SystemExit
)) {
95 // PyErr_Print() exists if SystemExit so we've to handle it ourselves
96 PyObject
*ty
= 0, *er
= 0, *tr
= 0;
97 PyErr_Fetch(&ty
, &er
, &tr
);
99 long err
= PyLong_AsLong(er
);
101 PrintAndLogEx(WARNING
, "\nScript terminated by " _YELLOW_("SystemExit %li"), err
);
122 if (set_file_name
&& PyDict_DelItemString(d
, "__file__")) {
128 #endif // HAVE_PYTHON
139 static int CmdHelp(const char *Cmd
);
143 #define PYTHON_LIBRARIES_WILDCARD "?.py"
145 static int split(char *str
, char **arr
) {
151 while (isspace(str
[begin_index
])) {
155 if (str
[begin_index
] == '\0') {
159 int end_index
= begin_index
;
160 while (str
[end_index
] && !isspace(str
[end_index
])) {
164 int len
= end_index
- begin_index
;
165 char *tmp
= calloc(len
+ 1, sizeof(char));
166 memcpy(tmp
, &str
[begin_index
], len
);
167 arr
[word_cnt
++] = tmp
;
168 begin_index
= end_index
;
173 static void set_python_path(const char *path
) {
174 PyObject
*syspath
= PySys_GetObject("path");
176 PrintAndLogEx(WARNING
, "Python failed to getobject");
179 PyObject
*pName
= PyUnicode_FromString(path
);
180 if (PyList_Insert(syspath
, 0, pName
)) {
181 PrintAndLogEx(WARNING
, "Error inserting extra path into sys.path list");
184 if (PySys_SetObject("path", syspath
)) {
185 PrintAndLogEx(WARNING
, "Error setting sys.path object");
189 static void set_python_paths(void) {
190 // Prepending to sys.path so we can load scripts from various places.
191 // This means the following directories are in reverse order of
192 // priority for search python modules.
194 // Allow current working directory because it seems that's what users want.
195 // But put it with lower search priority than the typical pm3 scripts directories
196 // but still with a higher priority than the pip installed libraries to mimic
197 // Python interpreter behavior. That should be confusing the users the least.
198 set_python_path(".");
199 const char *exec_path
= get_my_executable_directory();
200 if (exec_path
!= NULL
) {
201 // from the ./pyscripts/ directory
202 char scripts_path
[strlen(exec_path
) + strlen(PYTHON_SCRIPTS_SUBDIR
) + strlen(PYTHON_LIBRARIES_WILDCARD
) + 1];
203 strcpy(scripts_path
, exec_path
);
204 strcat(scripts_path
, PYTHON_SCRIPTS_SUBDIR
);
205 // strcat(scripts_path, PYTHON_LIBRARIES_WILDCARD);
206 set_python_path(scripts_path
);
209 const char *user_path
= get_my_user_directory();
210 if (user_path
!= NULL
) {
211 // from the $HOME/.proxmark3/pyscripts/ directory
212 char scripts_path
[strlen(user_path
) + strlen(PM3_USER_DIRECTORY
) + strlen(PYTHON_SCRIPTS_SUBDIR
) + strlen(PYTHON_LIBRARIES_WILDCARD
) + 1];
213 strcpy(scripts_path
, user_path
);
214 strcat(scripts_path
, PM3_USER_DIRECTORY
);
215 strcat(scripts_path
, PYTHON_SCRIPTS_SUBDIR
);
216 // strcat(scripts_path, PYTHON_LIBRARIES_WILDCARD);
217 set_python_path(scripts_path
);
221 if (exec_path
!= NULL
) {
222 // from the $PREFIX/share/proxmark3/pyscripts/ directory
223 char scripts_path
[strlen(exec_path
) + strlen(PM3_SHARE_RELPATH
) + strlen(PYTHON_SCRIPTS_SUBDIR
) + strlen(PYTHON_LIBRARIES_WILDCARD
) + 1];
224 strcpy(scripts_path
, exec_path
);
225 strcat(scripts_path
, PM3_SHARE_RELPATH
);
226 strcat(scripts_path
, PYTHON_SCRIPTS_SUBDIR
);
227 // strcat(scripts_path, PYTHON_LIBRARIES_WILDCARD);
228 set_python_path(scripts_path
);
234 * Generate a sorted list of available commands, what it does is
235 * generate a file listing of the script-directory for files
238 static int CmdScriptList(const char *Cmd
) {
239 CLIParserContext
*ctx
;
240 CLIParserInit(&ctx
, "script list",
241 "List available Lua, Cmd and Python scripts",
248 CLIExecWithReturn(ctx
, Cmd
, argtable
, true);
250 PrintAndLogEx(NORMAL
, "\n" _YELLOW_("[ Lua scripts ]"));
251 int ret
= searchAndList(LUA_SCRIPTS_SUBDIR
, ".lua");
252 if (ret
!= PM3_SUCCESS
) {
256 PrintAndLogEx(NORMAL
, "\n" _YELLOW_("[ Cmd scripts ]"));
257 ret
= searchAndList(CMD_SCRIPTS_SUBDIR
, ".cmd");
258 if (ret
!= PM3_SUCCESS
) {
263 PrintAndLogEx(NORMAL
, "\n" _YELLOW_("[ Python scripts ]"));
264 return searchAndList(PYTHON_SCRIPTS_SUBDIR
, ".py");
271 * @brief CmdScriptRun - executes a script file.
276 static int CmdScriptRun(const char *Cmd
) {
277 CLIParserContext
*ctx
;
278 CLIParserInit(&ctx
, "script run",
279 "Run a Lua, Cmd or Python script. "
280 "If no extension it will search for lua/cmd/py extensions\n"
281 "Use `script list` to see available scripts",
282 "script run my_script -h\n"
287 arg_file1(NULL
, NULL
, "<filename>", "name of script to run"),
288 arg_strx0(NULL
, NULL
, "<params>", "script parameters"),
293 char filename
[FILE_PATH_SIZE
] = {0};
295 char arguments
[1025] = {0};
297 sscanf(Cmd
, "%999s%n %1024[^\n\r]%n", filename
, &fnlen
, arguments
, &arg_len
);
300 // since we don't want to use "-f" for script filename,
301 // and be able to send in parameters into script meanwhile
302 // being able to "-h" here too.
303 if ((strlen(filename
) == 0) ||
304 (strcmp(filename
, "-h") == 0) ||
305 (strcmp(filename
, "--help") == 0)) {
306 ctx
->argtable
= argtable
;
307 ctx
->argtableLen
= arg_getsize(argtable
);
308 CLIParserPrintHelp(ctx
);
314 // try to detect a valid script file extension, case-insensitive
316 extension_chk
= str_dup(filename
);
317 str_lower(extension_chk
);
319 pm3_scriptfile_t ext
= PM3_UNSPECIFIED
;
320 if (str_endswith(extension_chk
, ".lua")) {
322 } else if (str_endswith(extension_chk
, ".cmd")) {
326 else if (str_endswith(extension_chk
, ".py")) {
332 static uint8_t luascriptfile_idx
= 0;
333 char *script_path
= NULL
;
334 if (((ext
== PM3_LUA
) || (ext
== PM3_UNSPECIFIED
)) && (searchFile(&script_path
, LUA_SCRIPTS_SUBDIR
, filename
, ".lua", true) == PM3_SUCCESS
)) {
336 if (luascriptfile_idx
== MAX_NESTED_LUASCRIPT
) {
337 PrintAndLogEx(ERR
, "too many nested scripts, skipping %s\n", script_path
);
341 PrintAndLogEx(SUCCESS
, "executing lua " _YELLOW_("%s"), script_path
);
342 PrintAndLogEx(SUCCESS
, "args " _YELLOW_("'%s'"), arguments
);
346 // create new Lua state
347 lua_State
*lua_state
;
348 lua_state
= luaL_newstate();
350 // load Lua libraries
351 luaL_openlibs(lua_state
);
353 //Sets the pm3 core libraries, that go a bit 'under the hood'
354 set_pm3_libraries(lua_state
);
356 //Add the 'bin' library
357 set_bin_library(lua_state
);
359 //Add the 'bit' library
360 set_bit_library(lua_state
);
362 luaL_requiref(lua_state
, "pm3", luaopen_pm3
, 1);
364 error
= luaL_loadfile(lua_state
, script_path
);
367 lua_pushstring(lua_state
, arguments
);
368 lua_setglobal(lua_state
, "args");
370 //Call it with 0 arguments
371 error
= lua_pcall(lua_state
, 0, LUA_MULTRET
, 0); // once again, returns non-0 on error,
373 if (error
) { // if non-0, then an error
374 // the top of the stack should be the error string
375 if (!lua_isstring(lua_state
, lua_gettop(lua_state
)))
376 PrintAndLogEx(FAILED
, "error - but no error (?!)");
378 // get the top of the stack as the error and pop it off
379 const char *str
= lua_tostring(lua_state
, lua_gettop(lua_state
));
380 lua_pop(lua_state
, 1);
381 PrintAndLogEx(FAILED
, _RED_("error") " - %s", str
);
384 //luaL_dofile(lua_state, buf);
385 // close the Lua state
386 lua_close(lua_state
);
388 PrintAndLogEx(SUCCESS
, "\nfinished " _YELLOW_("%s"), filename
);
392 if (((ext
== PM3_CMD
) || (ext
== PM3_UNSPECIFIED
)) && (searchFile(&script_path
, CMD_SCRIPTS_SUBDIR
, filename
, ".cmd", true) == PM3_SUCCESS
)) {
394 PrintAndLogEx(SUCCESS
, "executing Cmd " _YELLOW_("%s"), script_path
);
395 PrintAndLogEx(SUCCESS
, "args " _YELLOW_("'%s'"), arguments
);
397 int ret
= push_cmdscriptfile(script_path
, true);
398 if (ret
!= PM3_SUCCESS
)
399 PrintAndLogEx(ERR
, "could not open " _YELLOW_("%s") "...", script_path
);
405 For apt (Ubuntu, Debian...):
406 sudo apt-get install python3-dev # for python3.x installs
408 For yum (CentOS, RHEL...):
409 sudo yum install python3-devel # for python3.x installs
412 sudo dnf install python3-devel # for python3.x installs
414 For zypper (openSUSE...):
415 sudo zypper in python3-devel # for python3.x installs
419 # This is a departure from the normal Alpine naming
420 # scheme, which uses py2- and py3- prefixes
422 sudo apk add python3-dev # for python3.x installs
424 For apt-cyg (Cygwin...):
425 apt-cyg install python3-devel # for python3.x installs
430 if (((ext
== PM3_PY
) || (ext
== PM3_UNSPECIFIED
)) && (searchFile(&script_path
, PYTHON_SCRIPTS_SUBDIR
, filename
, ".py", true) == PM3_SUCCESS
)) {
432 PrintAndLogEx(SUCCESS
, "executing python " _YELLOW_("%s"), script_path
);
433 PrintAndLogEx(SUCCESS
, "args " _YELLOW_("'%s'"), arguments
);
435 #ifdef HAVE_PYTHON_SWIG
436 // hook Proxmark3 API
437 PyImport_AppendInittab("_pm3", PyInit__pm3
);
439 #if PY_MAJOR_VERSION == 3 && PY_MINOR_VERSION < 10
444 // We need to use Python mode instead of isolated to avoid breaking stuff.
445 PyConfig_InitPythonConfig(&py_conf
);
446 // Let's still make things bit safer by being as close as possible to isolated mode.
447 py_conf
.configure_c_stdio
= -1;
448 py_conf
.faulthandler
= 0;
449 py_conf
.use_hash_seed
= 0;
450 py_conf
.install_signal_handlers
= 0;
451 py_conf
.parse_argv
= 0;
452 py_conf
.user_site_directory
= 1;
453 py_conf
.use_environment
= 0;
456 //int argc, char ** argv
457 char *argv
[FILE_PATH_SIZE
];
458 argv
[0] = script_path
;
459 int argc
= split(arguments
, &argv
[1]);
460 #if PY_MAJOR_VERSION == 3 && PY_MINOR_VERSION < 10
461 wchar_t *py_args
[argc
+ 1];
462 for (int i
= 0; i
<= argc
; i
++) {
463 py_args
[i
] = Py_DecodeLocale(argv
[i
], NULL
);
466 PySys_SetArgv(argc
+ 1, py_args
);
468 // The following line will implicitly pre-initialize Python
469 status
= PyConfig_SetBytesArgv(&py_conf
, argc
+ 1, argv
);
470 if (PyStatus_Exception(status
)) {
473 // We disallowed in py_conf environment variables interfering with python interpreter's behavior.
474 // Let's manually enable the ones we truly need.
475 const char *virtual_env
= getenv("VIRTUAL_ENV");
476 if (virtual_env
!= NULL
) {
477 size_t length
= strlen(virtual_env
) + strlen("/bin/python3") + 1;
478 char python_executable_path
[length
];
479 snprintf(python_executable_path
, length
, "%s/bin/python3", virtual_env
);
480 status
= PyConfig_SetBytesString(&py_conf
, &py_conf
.executable
, python_executable_path
);
481 if (PyStatus_Exception(status
)) {
485 // This is required by Proxspace to work with an isolated Python configuration
486 status
= PyConfig_SetBytesString(&py_conf
, &py_conf
.home
, getenv("PYTHONHOME"));
487 if (PyStatus_Exception(status
)) {
491 // This is required for allowing `import pm3` in python scripts
492 status
= PyConfig_SetBytesString(&py_conf
, &py_conf
.pythonpath_env
, getenv("PYTHONPATH"));
493 if (PyStatus_Exception(status
)) {
497 status
= Py_InitializeFromConfig(&py_conf
);
498 if (PyStatus_Exception(status
)) {
503 PyConfig_Clear(&py_conf
);
505 for (int i
= 0; i
< argc
; ++i
) {
509 // setup search paths.
512 FILE *f
= fopen(script_path
, "r");
514 PrintAndLogEx(ERR
, "Could open file " _YELLOW_("%s"), script_path
);
518 int ret
= Pm3PyRun_SimpleFileNoExit(f
, filename
);
519 #if PY_MAJOR_VERSION == 3 && PY_MINOR_VERSION < 10
520 // Py_DecodeLocale() allocates memory that needs to be free'd
521 for (int i
= 0; i
< argc
+ 1; i
++) {
522 PyMem_RawFree(py_args
[i
]);
528 PrintAndLogEx(WARNING
, "\nfinished " _YELLOW_("%s") " with exception", filename
);
531 PrintAndLogEx(SUCCESS
, "\nfinished " _YELLOW_("%s"), filename
);
535 #if PY_MAJOR_VERSION == 3 && PY_MINOR_VERSION >= 10
537 PyConfig_Clear(&py_conf
);
538 if (PyStatus_IsExit(status
)) {
539 PrintAndLogEx(WARNING
, "\nPython initialization failed with exitcode=%i", status
.exitcode
);
541 if (PyStatus_IsError(status
)) {
542 PrintAndLogEx(WARNING
, "\nPython initialization failed with exception: %s", status
.err_msg
);
549 // file not found, let's search again to display the error messages
550 int ret
= PM3_EUNDEF
;
552 ret
= searchFile(&script_path
, LUA_SCRIPTS_SUBDIR
, filename
, ".lua", false);
553 else if (ext
== PM3_CMD
)
554 ret
= searchFile(&script_path
, CMD_SCRIPTS_SUBDIR
, filename
, ".cmd", false);
556 else if (ext
== PM3_PY
)
557 ret
= searchFile(&script_path
, PYTHON_SCRIPTS_SUBDIR
, filename
, ".py", false);
558 else if (ext
== PM3_UNSPECIFIED
)
559 PrintAndLogEx(FAILED
, "Error - can't find %s.[lua|cmd|py]", filename
);
561 else if (ext
== PM3_UNSPECIFIED
)
562 PrintAndLogEx(FAILED
, "Error - can't find %s.[lua|cmd]", filename
);
568 static command_t CommandTable
[] = {
569 {"help", CmdHelp
, AlwaysAvailable
, "This help"},
570 {"list", CmdScriptList
, AlwaysAvailable
, "List available scripts"},
571 {"run", CmdScriptRun
, AlwaysAvailable
, "<name> - execute a script"},
572 {NULL
, NULL
, NULL
, NULL
}
576 * Shows some basic help
581 static int CmdHelp(const char *Cmd
) {
582 (void)Cmd
; // Cmd is not used so far
584 PrintAndLogEx(NORMAL
, "This is a feature to run Lua/Cmd/Python scripts. You can place scripts within the luascripts/cmdscripts/pyscripts folders. ");
586 PrintAndLogEx(NORMAL
, "This is a feature to run Lua/Cmd scripts. You can place scripts within the luascripts/cmdscripts folders. ");
592 * Finds a matching script-file
597 int CmdScript(const char *Cmd
) {
598 clearCommandBuffer();
599 return CmdsParse(CommandTable
, Cmd
);