Merge pull request #2654 from Antiklesys/master
[RRG-proxmark3.git] / client / src / cmdscript.c
blobcc1d4000a13b0fac3e90f95c6107771a73316124
1 //-----------------------------------------------------------------------------
2 // Copyright (C) Proxmark3 contributors. See AUTHORS.md for details.
3 //
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.
8 //
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 //-----------------------------------------------------------------------------
19 #include <stdlib.h>
20 #include <string.h>
22 #ifdef HAVE_PYTHON
23 //#define PY_SSIZE_T_CLEAN
24 #include <Python.h>
25 #include <ctype.h>
26 #include <wchar.h>
27 #endif
29 #include "cmdparser.h" // command_t
30 #include "scripting.h"
31 #include "comms.h"
32 #include "cmdscript.h"
33 #include "cmdhfmf.h"
34 #include "pm3_binlib.h"
35 #include "pm3_bitlib.h"
36 #include "lualib.h"
37 #include "lauxlib.h"
38 #include "proxmark3.h"
39 #include "ui.h"
40 #include "fileutils.h"
41 #include "cliparser.h" // cliparsing
43 #ifdef HAVE_LUA_SWIG
44 extern int luaopen_pm3(lua_State *L);
45 #endif
47 #ifdef HAVE_PYTHON
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) {
56 PyObject *m, *d, *v;
57 int set_file_name = 0, ret = -1;
59 m = PyImport_AddModule("__main__");
60 if (m == NULL) {
61 return -1;
64 Py_INCREF(m);
65 d = PyModule_GetDict(m);
67 if (PyDict_GetItemString(d, "__file__") == NULL) {
69 PyObject *f;
70 f = PyUnicode_DecodeFSDefault(filename);
71 if (f == NULL) {
72 goto done;
75 if (PyDict_SetItemString(d, "__file__", f) < 0) {
76 Py_DECREF(f);
77 goto done;
80 if (PyDict_SetItemString(d, "__cached__", Py_None) < 0) {
81 Py_DECREF(f);
82 goto done;
85 set_file_name = 1;
86 Py_DECREF(f);
89 v = PyRun_FileExFlags(fp, filename, Py_file_input, d, d, 1, NULL);
90 if (v == NULL) {
92 Py_CLEAR(m);
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);
100 if (err) {
101 PrintAndLogEx(WARNING, "\nScript terminated by " _YELLOW_("SystemExit %li"), err);
102 } else {
103 ret = 0;
106 Py_DECREF(ty);
107 Py_DECREF(er);
108 Py_DECREF(er);
109 PyErr_Clear();
110 goto done;
112 } else {
113 PyErr_Print();
115 goto done;
118 Py_DECREF(v);
119 ret = 0;
121 done:
122 if (set_file_name && PyDict_DelItemString(d, "__file__")) {
123 PyErr_Clear();
125 Py_XDECREF(m);
126 return ret;
128 #endif // HAVE_PYTHON
130 typedef enum {
131 PM3_UNSPECIFIED,
132 PM3_LUA,
133 PM3_CMD,
134 #ifdef HAVE_PYTHON
135 PM3_PY
136 #endif
137 } pm3_scriptfile_t;
139 static int CmdHelp(const char *Cmd);
141 #ifdef HAVE_PYTHON
143 #define PYTHON_LIBRARIES_WILDCARD "?.py"
145 static int split(char *str, char **arr) {
146 int begin_index = 0;
147 int word_cnt = 0;
149 while (1) {
151 while (isspace(str[begin_index])) {
152 ++begin_index;
155 if (str[begin_index] == '\0') {
156 break;
159 int end_index = begin_index;
160 while (str[end_index] && !isspace(str[end_index])) {
161 ++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;
170 return word_cnt;
173 static void set_python_path(const char *path) {
174 PyObject *syspath = PySys_GetObject("path");
175 if (syspath == 0) {
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);
231 #endif
234 * Generate a sorted list of available commands, what it does is
235 * generate a file listing of the script-directory for files
236 * ending with .lua
238 static int CmdScriptList(const char *Cmd) {
239 CLIParserContext *ctx;
240 CLIParserInit(&ctx, "script list",
241 "List available Lua, Cmd and Python scripts",
242 "script list"
244 void *argtable[] = {
245 arg_param_begin,
246 arg_param_end
248 CLIExecWithReturn(ctx, Cmd, argtable, true);
249 CLIParserFree(ctx);
250 PrintAndLogEx(NORMAL, "\n" _YELLOW_("[ Lua scripts ]"));
251 int ret = searchAndList(LUA_SCRIPTS_SUBDIR, ".lua");
252 if (ret != PM3_SUCCESS) {
253 return ret;
256 PrintAndLogEx(NORMAL, "\n" _YELLOW_("[ Cmd scripts ]"));
257 ret = searchAndList(CMD_SCRIPTS_SUBDIR, ".cmd");
258 if (ret != PM3_SUCCESS) {
259 return ret;
262 #ifdef HAVE_PYTHON
263 PrintAndLogEx(NORMAL, "\n" _YELLOW_("[ Python scripts ]"));
264 return searchAndList(PYTHON_SCRIPTS_SUBDIR, ".py");
265 #else
266 return ret;
267 #endif
271 * @brief CmdScriptRun - executes a script file.
272 * @param argc
273 * @param argv
274 * @return
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"
285 void *argtable[] = {
286 arg_param_begin,
287 arg_file1(NULL, NULL, "<filename>", "name of script to run"),
288 arg_strx0(NULL, NULL, "<params>", "script parameters"),
289 arg_param_end
292 int fnlen = 0;
293 char filename[FILE_PATH_SIZE] = {0};
294 int arg_len = 0;
295 char arguments[1025] = {0};
297 sscanf(Cmd, "%999s%n %1024[^\n\r]%n", filename, &fnlen, arguments, &arg_len);
299 // hack
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);
309 CLIParserFree(ctx);
310 return PM3_ESOFT;
312 CLIParserFree(ctx);
314 // try to detect a valid script file extension, case-insensitive
315 char *extension_chk;
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")) {
321 ext = PM3_LUA;
322 } else if (str_endswith(extension_chk, ".cmd")) {
323 ext = PM3_CMD;
325 #ifdef HAVE_PYTHON
326 else if (str_endswith(extension_chk, ".py")) {
327 ext = PM3_PY;
329 #endif
330 free(extension_chk);
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)) {
335 int error;
336 if (luascriptfile_idx == MAX_NESTED_LUASCRIPT) {
337 PrintAndLogEx(ERR, "too many nested scripts, skipping %s\n", script_path);
338 free(script_path);
339 return PM3_EMALLOC;
341 PrintAndLogEx(SUCCESS, "executing lua " _YELLOW_("%s"), script_path);
342 PrintAndLogEx(SUCCESS, "args " _YELLOW_("'%s'"), arguments);
344 luascriptfile_idx++;
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);
361 #ifdef HAVE_LUA_SWIG
362 luaL_requiref(lua_state, "pm3", luaopen_pm3, 1);
363 #endif
364 error = luaL_loadfile(lua_state, script_path);
365 free(script_path);
366 if (!error) {
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);
387 luascriptfile_idx--;
388 PrintAndLogEx(SUCCESS, "\nfinished " _YELLOW_("%s"), filename);
389 return PM3_SUCCESS;
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);
400 free(script_path);
401 return ret;
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
411 For dnf (Fedora...):
412 sudo dnf install python3-devel # for python3.x installs
414 For zypper (openSUSE...):
415 sudo zypper in python3-devel # for python3.x installs
417 For apk (Alpine...):
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
428 #ifdef HAVE_PYTHON
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);
438 #endif
439 #if PY_MAJOR_VERSION == 3 && PY_MINOR_VERSION < 10
440 Py_Initialize();
441 #else
442 PyConfig py_conf;
443 PyStatus status;
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;
454 #endif
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);
467 #else
468 // The following line will implicitly pre-initialize Python
469 status = PyConfig_SetBytesArgv(&py_conf, argc + 1, argv);
470 if (PyStatus_Exception(status)) {
471 goto pyexception;
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)) {
482 goto pyexception;
484 } else {
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)) {
488 goto pyexception;
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)) {
494 goto pyexception;
497 status = Py_InitializeFromConfig(&py_conf);
498 if (PyStatus_Exception(status)) {
499 goto pyexception;
502 // clean up
503 PyConfig_Clear(&py_conf);
504 #endif
505 for (int i = 0; i < argc; ++i) {
506 free(argv[i + 1]);
509 // setup search paths.
510 set_python_paths();
512 FILE *f = fopen(script_path, "r");
513 if (f == NULL) {
514 PrintAndLogEx(ERR, "Could open file " _YELLOW_("%s"), script_path);
515 free(script_path);
516 return PM3_ESOFT;
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]);
524 #endif
525 Py_Finalize();
526 free(script_path);
527 if (ret) {
528 PrintAndLogEx(WARNING, "\nfinished " _YELLOW_("%s") " with exception", filename);
529 return PM3_ESOFT;
530 } else {
531 PrintAndLogEx(SUCCESS, "\nfinished " _YELLOW_("%s"), filename);
532 return PM3_SUCCESS;
535 #if PY_MAJOR_VERSION == 3 && PY_MINOR_VERSION >= 10
536 pyexception:
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);
544 return PM3_ESOFT;
545 #endif
547 #endif
549 // file not found, let's search again to display the error messages
550 int ret = PM3_EUNDEF;
551 if (ext == PM3_LUA)
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);
555 #ifdef HAVE_PYTHON
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);
560 #else
561 else if (ext == PM3_UNSPECIFIED)
562 PrintAndLogEx(FAILED, "Error - can't find %s.[lua|cmd]", filename);
563 #endif
564 free(script_path);
565 return ret;
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
577 * @brief CmdHelp
578 * @param Cmd
579 * @return
581 static int CmdHelp(const char *Cmd) {
582 (void)Cmd; // Cmd is not used so far
583 #ifdef HAVE_PYTHON
584 PrintAndLogEx(NORMAL, "This is a feature to run Lua/Cmd/Python scripts. You can place scripts within the luascripts/cmdscripts/pyscripts folders. ");
585 #else
586 PrintAndLogEx(NORMAL, "This is a feature to run Lua/Cmd scripts. You can place scripts within the luascripts/cmdscripts folders. ");
587 #endif
588 return PM3_SUCCESS;
592 * Finds a matching script-file
593 * @brief CmdScript
594 * @param Cmd
595 * @return
597 int CmdScript(const char *Cmd) {
598 clearCommandBuffer();
599 return CmdsParse(CommandTable, Cmd);