textual
[RRG-proxmark3.git] / client / src / cmdscript.c
blob09a324485850f94e5cba14a124203e74d3cc3abd
1 //-----------------------------------------------------------------------------
2 // Copyright (C) 2013 m h swende <martin at swende.se>
3 //
4 // This code is licensed to you under the terms of the GNU GPL, version 2 or,
5 // at your option, any later version. See the LICENSE.txt file for the text of
6 // the license.
7 //-----------------------------------------------------------------------------
8 // Some lua scripting glue to proxmark core.
9 //-----------------------------------------------------------------------------
10 // 2020, added Python support (@iceman1001)
12 #include <stdlib.h>
13 #include <string.h>
15 #ifdef HAVE_PYTHON
16 //#define PY_SSIZE_T_CLEAN
17 #include <Python.h>
18 #include <wchar.h>
19 #endif
21 #include "cmdparser.h" // command_t
22 #include "scripting.h"
23 #include "comms.h"
24 #include "cmdscript.h"
25 #include "cmdhfmf.h"
26 #include "pm3_binlib.h"
27 #include "pm3_bitlib.h"
28 #include "lualib.h"
29 #include "lauxlib.h"
30 #include "proxmark3.h"
31 #include "ui.h"
32 #include "fileutils.h"
33 #include "cliparser.h" // cliparsing
35 #ifdef HAVE_LUA_SWIG
36 extern int luaopen_pm3(lua_State *L);
37 #endif
39 #ifdef HAVE_PYTHON
40 #ifdef HAVE_PYTHON_SWIG
41 extern PyObject *PyInit__pm3(void);
42 #endif // HAVE_PYTHON_SWIG
44 // Partly ripped from PyRun_SimpleFileExFlags
45 // but does not terminate client on sys.exit
46 // and print exit code only if != 0
47 static int Pm3PyRun_SimpleFileNoExit(FILE *fp, const char *filename) {
48 PyObject *m, *d, *v;
49 int set_file_name = 0, ret = -1;
50 m = PyImport_AddModule("__main__");
51 if (m == NULL)
52 return -1;
53 Py_INCREF(m);
54 d = PyModule_GetDict(m);
55 if (PyDict_GetItemString(d, "__file__") == NULL) {
56 PyObject *f;
57 f = PyUnicode_DecodeFSDefault(filename);
58 if (f == NULL)
59 goto done;
60 if (PyDict_SetItemString(d, "__file__", f) < 0) {
61 Py_DECREF(f);
62 goto done;
64 if (PyDict_SetItemString(d, "__cached__", Py_None) < 0) {
65 Py_DECREF(f);
66 goto done;
68 set_file_name = 1;
69 Py_DECREF(f);
71 v = PyRun_FileExFlags(fp, filename, Py_file_input, d, d, 1, NULL);
72 if (v == NULL) {
73 Py_CLEAR(m);
74 if (PyErr_ExceptionMatches(PyExc_SystemExit)) {
75 // PyErr_Print() exists if SystemExit so we've to handle it ourselves
76 PyObject *ty = 0, *er = 0, *tr = 0;
77 PyErr_Fetch(&ty, &er, &tr);
78 long err = PyLong_AsLong(er);
79 if (err) {
80 PrintAndLogEx(WARNING, "\nScript terminated by " _YELLOW_("SystemExit %li"), err);
81 } else {
82 ret = 0;
84 Py_DECREF(ty);
85 Py_DECREF(er);
86 Py_DECREF(er);
87 PyErr_Clear();
88 goto done;
89 } else {
90 PyErr_Print();
92 goto done;
94 Py_DECREF(v);
95 ret = 0;
96 done:
97 if (set_file_name && PyDict_DelItemString(d, "__file__"))
98 PyErr_Clear();
99 Py_XDECREF(m);
100 return ret;
102 #endif // HAVE_PYTHON
104 typedef enum {
105 PM3_UNSPECIFIED,
106 PM3_LUA,
107 PM3_CMD,
108 #ifdef HAVE_PYTHON
109 PM3_PY
110 #endif
111 } pm3_scriptfile_t;
113 static int CmdHelp(const char *Cmd);
115 #ifdef HAVE_PYTHON
117 #define PYTHON_LIBRARIES_WILDCARD "?.py"
119 static int split(char *str, char **arr) {
120 int begin_index = 0;
121 int word_cnt = 0;
123 while (1) {
124 while (isspace(str[begin_index])) {
125 ++begin_index;
127 if (str[begin_index] == '\0') {
128 break;
130 int end_index = begin_index;
131 while (str[end_index] && !isspace(str[end_index])) {
132 ++end_index;
134 int len = end_index - begin_index;
135 char *tmp = calloc(len + 1, sizeof(char));
136 memcpy(tmp, &str[begin_index], len);
137 arr[word_cnt++] = tmp;
138 begin_index = end_index;
140 return word_cnt;
143 static void set_python_path(char *path) {
144 PyObject *syspath = PySys_GetObject("path");
145 if (syspath == 0) {
146 PrintAndLogEx(WARNING, "Python failed to getobject");
149 PyObject *pName = PyUnicode_FromString(path);
150 if (PyList_Insert(syspath, 0, pName)) {
151 PrintAndLogEx(WARNING, "Error inserting extra path into sys.path list");
154 if (PySys_SetObject("path", syspath)) {
155 PrintAndLogEx(WARNING, "Error setting sys.path object");
159 static void set_python_paths(void) {
160 //--add to the LUA_PATH (package.path in lua)
161 // so we can load scripts from various places:
162 const char *exec_path = get_my_executable_directory();
163 if (exec_path != NULL) {
164 // from the ./luascripts/ directory
165 char scripts_path[strlen(exec_path) + strlen(PYTHON_SCRIPTS_SUBDIR) + strlen(PYTHON_LIBRARIES_WILDCARD) + 1];
166 strcpy(scripts_path, exec_path);
167 strcat(scripts_path, PYTHON_SCRIPTS_SUBDIR);
168 // strcat(scripts_path, PYTHON_LIBRARIES_WILDCARD);
169 set_python_path(scripts_path);
172 const char *user_path = get_my_user_directory();
173 if (user_path != NULL) {
174 // from the $HOME/.proxmark3/luascripts/ directory
175 char scripts_path[strlen(user_path) + strlen(PM3_USER_DIRECTORY) + strlen(PYTHON_SCRIPTS_SUBDIR) + strlen(PYTHON_LIBRARIES_WILDCARD) + 1];
176 strcpy(scripts_path, user_path);
177 strcat(scripts_path, PM3_USER_DIRECTORY);
178 strcat(scripts_path, PYTHON_SCRIPTS_SUBDIR);
179 // strcat(scripts_path, PYTHON_LIBRARIES_WILDCARD);
180 set_python_path(scripts_path);
184 if (exec_path != NULL) {
185 // from the $PREFIX/share/proxmark3/luascripts/ directory
186 char scripts_path[strlen(exec_path) + strlen(PM3_SHARE_RELPATH) + strlen(PYTHON_SCRIPTS_SUBDIR) + strlen(PYTHON_LIBRARIES_WILDCARD) + 1];
187 strcpy(scripts_path, exec_path);
188 strcat(scripts_path, PM3_SHARE_RELPATH);
189 strcat(scripts_path, PYTHON_SCRIPTS_SUBDIR);
190 // strcat(scripts_path, PYTHON_LIBRARIES_WILDCARD);
191 set_python_path(scripts_path);
194 #endif
197 * Generate a sorted list of available commands, what it does is
198 * generate a file listing of the script-directory for files
199 * ending with .lua
201 static int CmdScriptList(const char *Cmd) {
202 CLIParserContext *ctx;
203 CLIParserInit(&ctx, "script list",
204 "List available Lua, Cmd and Python scripts",
205 "script list"
207 void *argtable[] = {
208 arg_param_begin,
209 arg_param_end
211 CLIExecWithReturn(ctx, Cmd, argtable, true);
212 CLIParserFree(ctx);
213 PrintAndLogEx(NORMAL, "\n" _YELLOW_("[ Lua scripts ]"));
214 int ret = searchAndList(LUA_SCRIPTS_SUBDIR, ".lua");
215 if (ret != PM3_SUCCESS)
216 return ret;
218 PrintAndLogEx(NORMAL, "\n" _YELLOW_("[ Cmd scripts ]"));
219 ret = searchAndList(CMD_SCRIPTS_SUBDIR, ".cmd");
220 if (ret != PM3_SUCCESS)
221 return ret;
222 #ifdef HAVE_PYTHON
223 PrintAndLogEx(NORMAL, "\n" _YELLOW_("[ Python scripts ]"));
224 return searchAndList(PYTHON_SCRIPTS_SUBDIR, ".py");
225 #else
226 return ret;
227 #endif
231 * @brief CmdScriptRun - executes a script file.
232 * @param argc
233 * @param argv
234 * @return
236 static int CmdScriptRun(const char *Cmd) {
237 CLIParserContext *ctx;
238 CLIParserInit(&ctx, "script run",
239 "Run a Lua, Cmd or Python script. "
240 "If no extension it will search for lua/cmd/py extensions\n"
241 "Use `script list` to see available scripts",
242 "script run my_script -h\n"
245 void *argtable[] = {
246 arg_param_begin,
247 arg_file1(NULL, NULL, "<filename>", "name of script to run"),
248 arg_strx0(NULL, NULL, "<params>", "script parameters"),
249 arg_param_end
252 int fnlen = 0;
253 char filename[128] = {0};
254 int arg_len = 0;
255 char arguments[256] = {0};
257 sscanf(Cmd, "%127s%n %255[^\n\r]%n", filename, &fnlen, arguments, &arg_len);
259 // hack
260 // since we don't want to use "-f" for script filename,
261 // and be able to send in parameters into script meanwhile
262 // being able to "-h" here too.
263 if ((strlen(filename) == 0) ||
264 (strcmp(filename, "-h") == 0) ||
265 (strcmp(filename, "--help") == 0)) {
266 ctx->argtable = argtable;
267 ctx->argtableLen = arg_getsize(argtable);
268 CLIParserPrintHelp(ctx);
269 CLIParserFree(ctx);
270 return PM3_ESOFT;
272 CLIParserFree(ctx);
274 // try to detect a valid script file extention, case-insensitive
275 char *extension_chk;
276 extension_chk = str_dup(filename);
277 str_lower(extension_chk);
279 pm3_scriptfile_t ext = PM3_UNSPECIFIED;
280 if (str_endswith(extension_chk, ".lua")) {
281 ext = PM3_LUA;
282 } else if (str_endswith(extension_chk, ".cmd")) {
283 ext = PM3_CMD;
285 #ifdef HAVE_PYTHON
286 else if (str_endswith(extension_chk, ".py")) {
287 ext = PM3_PY;
289 #endif
290 free(extension_chk);
292 static uint8_t luascriptfile_idx = 0;
293 char *script_path = NULL;
294 if (((ext == PM3_LUA) || (ext == PM3_UNSPECIFIED)) && (searchFile(&script_path, LUA_SCRIPTS_SUBDIR, filename, ".lua", true) == PM3_SUCCESS)) {
295 int error;
296 if (luascriptfile_idx == MAX_NESTED_LUASCRIPT) {
297 PrintAndLogEx(ERR, "too many nested scripts, skipping %s\n", script_path);
298 free(script_path);
299 return PM3_EMALLOC;
301 PrintAndLogEx(SUCCESS, "executing lua " _YELLOW_("%s"), script_path);
302 PrintAndLogEx(SUCCESS, "args " _YELLOW_("'%s'"), arguments);
304 luascriptfile_idx++;
306 // create new Lua state
307 lua_State *lua_state;
308 lua_state = luaL_newstate();
310 // load Lua libraries
311 luaL_openlibs(lua_state);
313 //Sets the pm3 core libraries, that go a bit 'under the hood'
314 set_pm3_libraries(lua_state);
316 //Add the 'bin' library
317 set_bin_library(lua_state);
319 //Add the 'bit' library
320 set_bit_library(lua_state);
321 #ifdef HAVE_LUA_SWIG
322 luaL_requiref(lua_state, "pm3", luaopen_pm3, 1);
323 #endif
324 error = luaL_loadfile(lua_state, script_path);
325 free(script_path);
326 if (!error) {
327 lua_pushstring(lua_state, arguments);
328 lua_setglobal(lua_state, "args");
330 //Call it with 0 arguments
331 error = lua_pcall(lua_state, 0, LUA_MULTRET, 0); // once again, returns non-0 on error,
333 if (error) { // if non-0, then an error
334 // the top of the stack should be the error string
335 if (!lua_isstring(lua_state, lua_gettop(lua_state)))
336 PrintAndLogEx(FAILED, "error - but no error (?!)");
338 // get the top of the stack as the error and pop it off
339 const char *str = lua_tostring(lua_state, lua_gettop(lua_state));
340 lua_pop(lua_state, 1);
341 PrintAndLogEx(FAILED, _RED_("error") " - %s", str);
344 //luaL_dofile(lua_state, buf);
345 // close the Lua state
346 lua_close(lua_state);
347 luascriptfile_idx--;
348 PrintAndLogEx(SUCCESS, "\nfinished " _YELLOW_("%s"), filename);
349 return PM3_SUCCESS;
352 if (((ext == PM3_CMD) || (ext == PM3_UNSPECIFIED)) && (searchFile(&script_path, CMD_SCRIPTS_SUBDIR, filename, ".cmd", true) == PM3_SUCCESS)) {
354 PrintAndLogEx(SUCCESS, "executing Cmd " _YELLOW_("%s"), script_path);
355 PrintAndLogEx(SUCCESS, "args " _YELLOW_("'%s'"), arguments);
357 int ret = push_cmdscriptfile(script_path, true);
358 if (ret != PM3_SUCCESS)
359 PrintAndLogEx(ERR, "could not open " _YELLOW_("%s") "...", script_path);
360 free(script_path);
361 return ret;
365 For apt (Ubuntu, Debian...):
366 sudo apt-get install python3-dev # for python3.x installs
368 For yum (CentOS, RHEL...):
369 sudo yum install python3-devel # for python3.x installs
371 For dnf (Fedora...):
372 sudo dnf install python3-devel # for python3.x installs
374 For zypper (openSUSE...):
375 sudo zypper in python3-devel # for python3.x installs
377 For apk (Alpine...):
379 # This is a departure from the normal Alpine naming
380 # scheme, which uses py2- and py3- prefixes
382 sudo apk add python3-dev # for python3.x installs
384 For apt-cyg (Cygwin...):
385 apt-cyg install python3-devel # for python3.x installs
388 #ifdef HAVE_PYTHON
390 if (((ext == PM3_PY) || (ext == PM3_UNSPECIFIED)) && (searchFile(&script_path, PYTHON_SCRIPTS_SUBDIR, filename, ".py", true) == PM3_SUCCESS)) {
392 PrintAndLogEx(SUCCESS, "executing python " _YELLOW_("%s"), script_path);
393 PrintAndLogEx(SUCCESS, "args " _YELLOW_("'%s'"), arguments);
395 wchar_t *program = Py_DecodeLocale(filename, NULL);
396 if (program == NULL) {
397 PrintAndLogEx(ERR, "could not decode " _YELLOW_("%s"), filename);
398 free(script_path);
399 return PM3_ESOFT;
402 // optional but recommended
403 Py_SetProgramName(program);
404 #ifdef HAVE_PYTHON_SWIG
405 // hook Proxmark3 API
406 PyImport_AppendInittab("_pm3", PyInit__pm3);
407 #endif
408 Py_Initialize();
410 //int argc, char ** argv
411 char *argv[128];
412 int argc = split(arguments, argv);
413 wchar_t *py_args[argc];
414 py_args[0] = Py_DecodeLocale(filename, NULL);
415 for (int i = 0; i < argc; i++) {
416 py_args[i + 1] = Py_DecodeLocale(argv[i], NULL);
419 PySys_SetArgv(argc + 1, py_args);
421 // clean up
422 for (int i = 0; i < argc; ++i) {
423 free(argv[i]);
426 // setup search paths.
427 set_python_paths();
429 FILE *f = fopen(script_path, "r");
430 if (f == NULL) {
431 PrintAndLogEx(ERR, "Could open file " _YELLOW_("%s"), script_path);
432 free(script_path);
433 return PM3_ESOFT;
435 int ret = Pm3PyRun_SimpleFileNoExit(f, filename);
436 Py_Finalize();
437 PyMem_RawFree(program);
438 free(script_path);
439 if (ret) {
440 PrintAndLogEx(WARNING, "\nfinished " _YELLOW_("%s") " with exception", filename);
441 return PM3_ESOFT;
442 } else {
443 PrintAndLogEx(SUCCESS, "\nfinished " _YELLOW_("%s"), filename);
444 return PM3_SUCCESS;
447 #endif
449 // file not found, let's search again to display the error messages
450 int ret = PM3_EUNDEF;
451 if (ext == PM3_LUA)
452 ret = searchFile(&script_path, LUA_SCRIPTS_SUBDIR, filename, ".lua", false);
453 else if (ext == PM3_CMD)
454 ret = searchFile(&script_path, CMD_SCRIPTS_SUBDIR, filename, ".cmd", false);
455 #ifdef HAVE_PYTHON
456 else if (ext == PM3_PY)
457 ret = searchFile(&script_path, PYTHON_SCRIPTS_SUBDIR, filename, ".py", false);
458 else if (ext == PM3_UNSPECIFIED)
459 PrintAndLogEx(FAILED, "Error - can't find %s.[lua|cmd|py]", filename);
460 #else
461 else if (ext == PM3_UNSPECIFIED)
462 PrintAndLogEx(FAILED, "Error - can't find %s.[lua|cmd]", filename);
463 #endif
464 free(script_path);
465 return ret;
468 static command_t CommandTable[] = {
469 {"help", CmdHelp, AlwaysAvailable, "This help"},
470 {"list", CmdScriptList, AlwaysAvailable, "List available scripts"},
471 {"run", CmdScriptRun, AlwaysAvailable, "<name> - execute a script"},
472 {NULL, NULL, NULL, NULL}
476 * Shows some basic help
477 * @brief CmdHelp
478 * @param Cmd
479 * @return
481 static int CmdHelp(const char *Cmd) {
482 (void)Cmd; // Cmd is not used so far
483 #ifdef HAVE_PYTHON
484 PrintAndLogEx(NORMAL, "This is a feature to run Lua/Cmd/Python scripts. You can place scripts within the luascripts/cmdscripts/pyscripts folders. ");
485 #else
486 PrintAndLogEx(NORMAL, "This is a feature to run Lua/Cmd scripts. You can place scripts within the luascripts/cmdscripts folders. ");
487 #endif
488 return PM3_SUCCESS;
492 * Finds a matching script-file
493 * @brief CmdScript
494 * @param Cmd
495 * @return
497 int CmdScript(const char *Cmd) {
498 clearCommandBuffer();
499 return CmdsParse(CommandTable, Cmd);