/* * Copyright (C) 2009 J. Ali Harlow * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "config.h" #include #include #include #include #include #include #include #include #include #include "razor.h" #include "razor-internal.h" #define MAX_ARGS 2 #define ARG_FLAG_OPT 0x1 #define ARG_FLAG_BYPASS 0x2 #define ARG(flags, swtch, index, type) \ ((flags) << 26 | (swtch) << 21 | \ (index) << 16 | type) #define ARG_FLAGS(arg) ((unsigned)(arg) >> 26) #define ARG_SWTCH(arg) ((unsigned)(arg) >> 21 & 0x1F) #define ARG_INDEX(arg) ((unsigned)(arg) >> 16 & 0x1F) #define ARG_TYPE(arg) ((unsigned)(arg) & 0xFFFF) #define ARG_NONE 0 #define ARG_STRING(n) ARG(0, 0, n, LUA_TSTRING) #define ARG_OPT_STRING(n) ARG(ARG_FLAG_OPT, 0, n, LUA_TSTRING) #define ARG_BYP_STRING(n, s) ARG(ARG_FLAG_BYPASS, s, n, LUA_TSTRING) struct razor_proxy { char *table, *name; int result; unsigned args[MAX_ARGS]; }; static struct razor_proxy proxy[] = { { NULL, "dofile", LUA_TNONE, { ARG_STRING(1) } }, { NULL, "loadfile", LUA_TNONE, { ARG_OPT_STRING(1) } }, { "io", "input", LUA_TNONE, { ARG_OPT_STRING(1) } }, { "io", "output", LUA_TNONE, { ARG_OPT_STRING(1) } }, { "io", "lines", LUA_TNONE, { ARG_OPT_STRING(1) } }, { "io", "open", LUA_TNONE, { ARG_STRING(1) } }, { "io", "popen", LUA_TNONE, { ARG_STRING(1) } }, /* * Note: We do not proxy os.execute * We can't do it properly (there is no way to distinguish filenames * in arguments from any other strings) so it's better not to try. * Scripts that attempt to be portable should use posix.exec[p] */ { "os", "remove", LUA_TNONE, { ARG_STRING(1) } }, { "os", "rename", LUA_TNONE, { ARG_STRING(1), ARG_STRING(2) } }, /* * Note: We do not proxy os.tmpname * It should never be used. scripts should use io.tmpfile instead * if the rather limited functionality it provides suffices. The * proper solution is to implement posix.mkstemp */ { "posix", "access", LUA_TNONE, { ARG_STRING(1) } }, { "posix", "chdir", LUA_TNONE, { ARG_STRING(1) } }, { "posix", "chmod", LUA_TNONE, { ARG_STRING(1) } }, { "posix", "chown", LUA_TNONE, { ARG_STRING(1) } }, { "posix", "dir", LUA_TNONE, { ARG_OPT_STRING(1) } }, { "posix", "exec", LUA_TNONE, { ARG_STRING(1) } }, { "posix", "execp", LUA_TNONE, { ARG_STRING(1) } }, { "posix", "files", LUA_TNONE, { ARG_OPT_STRING(1) } }, { "posix", "getcwd", LUA_TSTRING, { ARG_NONE } }, { "posix", "glob", LUA_TTABLE, { ARG_STRING(1) } }, { "posix", "link", LUA_TNONE, { ARG_BYP_STRING(1, 3), ARG_STRING(2) } }, { "posix", "mkdir", LUA_TNONE, { ARG_STRING(1) } }, { "posix", "mkfifo", LUA_TNONE, { ARG_STRING(1) } }, { "posix", "pathconf", LUA_TNONE, { ARG_STRING(1) } }, { "posix", "readlink", LUA_TNONE, { ARG_STRING(1) } }, { "posix", "rmdir", LUA_TNONE, { ARG_STRING(1) } }, { "posix", "stat", LUA_TNONE, { ARG_STRING(1) } }, { "posix", "unlink", LUA_TNONE, { ARG_STRING(1) } }, { "posix", "utime", LUA_TNONE, { ARG_STRING(1) } }, }; static lua_CFunction execp_handler; struct razor_lua { const char *root; lua_CFunction handler[ARRAY_SIZE(proxy)]; }; static void *alloc_lua(void *user_data, void *ptr, size_t osize, size_t nsize) { if (!nsize) { free(ptr); return NULL; } else return realloc(ptr, nsize); } static int proxy_execp(lua_State *L) { int r, n, err, got_eacces = 0; char *file = strdup(luaL_checkstring(L, 1)); char *exe; const char *s, *e, *path; struct razor_lua *rl; if (*file == '\0' || strchr(file, '/')) { r = execp_handler(L); free(file); return r; } (void)lua_getallocf(L, (void **)&rl); path = getenv("PATH"); if (!path) path = ":/bin:/usr/bin"; s = path; for (;;) { e = strchr(s, ':'); if (!e) e = s + strlen(s); if (*s == '/') { n = strlen(rl->root); exe = malloc(n + (e - s) + 1 + strlen(file) + 1); memcpy(exe, rl->root, n); memcpy(exe + n, s, e - s); n += e - s; exe[n++] = '/'; strcpy(exe + n, file); } else if (e != s) { exe = malloc((e - s) + 1 + strlen(file) + 1); memcpy(exe, s, e - s); n = e - s; exe[n++] = '/'; strcpy(exe + n, file); } else { /* Ensure exe contains a slash to avoid PATH search */ exe = malloc(2 + strlen(file) + 1); exe[0] = '.'; exe[1] = '/'; strcpy(exe + 2, file); } lua_pushstring(L, exe); lua_replace(L, 1); free(exe); n = lua_gettop(L); r = execp_handler(L); err = lua_tointeger(L, -1); switch (err) { case EACCES: got_eacces = 1; /* Fall through */ case ENOENT: case ESTALE: case ENOTDIR: case ENODEV: case ETIMEDOUT: lua_pop(L, lua_gettop(L) - n); break; default: free(file); return r; } if (*e) s = e + 1; else break; } if (got_eacces) err = EACCES; lua_pushnil(L); lua_pushfstring(L, "%s: %s", file, strerror(err)); lua_pushinteger(L, err); free(file); return 3; } static int real2fake(lua_State *L, struct razor_lua *rl, int indx) { const char *path; if (indx < 0) indx += lua_gettop(L) + 1; if (lua_type(L, indx) == LUA_TSTRING) { path = lua_tostring(L, indx); if (path && !strncmp(path, rl->root, strlen(rl->root)) && (path[strlen(rl->root)] == '/' || !path[strlen(rl->root)])) { lua_pushstring(L, path + strlen(rl->root)); lua_replace(L, indx); return 1; } } return 0; } static int proxy_handler(lua_State *L) { int i, retval, cond; unsigned arg; const char *path; struct razor_lua *rl; int method = lua_tointeger(L, lua_upvalueindex(1)); struct razor_proxy *rp = proxy + method; (void)lua_getallocf(L, (void **)&rl); for(i = 0; i < MAX_ARGS; i++) { arg = rp->args[i]; if (arg == ARG_NONE) continue; switch (ARG_TYPE(arg)) { case LUA_TSTRING: if (ARG_FLAGS(arg) & ARG_FLAG_BYPASS) cond = !lua_toboolean(L, ARG_SWTCH(arg)); else cond = 1; if (ARG_FLAGS(arg) & ARG_FLAG_OPT) { if (lua_isstring(L, ARG_INDEX(arg))) path = lua_tostring(L, ARG_INDEX(arg)); else path = NULL; } else path = luaL_checkstring(L, ARG_INDEX(arg)); if (cond && path && *path == '/') { lua_pushfstring(L, "%s%s", rl->root, path); lua_replace(L, ARG_INDEX(arg)); } break; default: lua_pushfstring(L, "razor proxy: Unhandled type (%d)", ARG_TYPE(arg)); lua_error(L); } } retval = rl->handler[method](L); if (rp->result != LUA_TNONE && !lua_isnil(L, 1)) { switch (rp->result) { case LUA_TNONE: break; case LUA_TSTRING: real2fake(L, rl, -1); break; case LUA_TTABLE: lua_pushnil(L); while (lua_next(L, -2)) { if (real2fake(L, rl, -1)) { i = lua_tonumber(L, -2); lua_rawseti(L, -3, i); } else lua_pop(L, 1); } break; default: lua_pushfstring(L, "razor proxy: Unhandled type (%d)", rp->result); lua_error(L); } } return retval; } struct razor_lua_loader { uint32_t name; lua_CFunction func; }; static struct razor_preload { int init; struct hashtable modules; struct array name_pool; struct array loaders; } razor_preload = {0}; RAZOR_EXPORT void razor_set_lua_loader(const char *modname, void (*loader)()) { uint32_t name; struct razor_lua_loader *ploader, *end; if (!razor_preload.init) { razor_preload.init = 1; array_init(&razor_preload.name_pool); array_init(&razor_preload.loaders); hashtable_init(&razor_preload.modules, &razor_preload.name_pool); } name = hashtable_tokenize(&razor_preload.modules, modname); end = razor_preload.loaders.data + razor_preload.loaders.size; for(ploader = razor_preload.loaders.data; ploader < end; ploader++) if (ploader->name == name) { ploader->func = loader; return; } ploader = array_add(&razor_preload.loaders, sizeof(*ploader)); ploader->name = name; ploader->func = loader; } static void razor_lua_preload(lua_State *L) { struct razor_lua_loader *ploader, *end; if (!razor_preload.init) return; lua_getfield(L, LUA_GLOBALSINDEX, "package"); lua_getfield(L, -1, "preload"); lua_remove(L, -2); end = razor_preload.loaders.data + razor_preload.loaders.size; for(ploader = razor_preload.loaders.data; ploader < end; ploader++) { lua_pushcfunction(L, ploader->func); lua_setfield(L, -2, razor_preload.name_pool.data + ploader->name); } lua_pop(L, 1); } int run_lua_script(const char *root, const char *name, const char *body, ssize_t len) { int i, n; lua_State *L; struct razor_lua rl; if (root && strcmp(root, "/")) rl.root = root; else rl.root = NULL; L = lua_newstate(alloc_lua, &rl); luaL_openlibs(L); razor_lua_preload(L); lua_getglobal(L, "require"); lua_pushstring(L, "posix"); if (lua_pcall(L, 1, 1, 0)) { fprintf(stderr, "lua posix: %s\n", lua_tostring(L, -1)); lua_pop(L, 1); lua_close(L); return -1; } if (rl.root) for(i = 0; i < ARRAY_SIZE(proxy); i++) { if (proxy[i].table) { lua_getglobal(L, proxy[i].table); n = lua_gettop(L); } else n = LUA_GLOBALSINDEX; lua_getfield(L, n, proxy[i].name); rl.handler[i] = lua_tocfunction(L, -1); lua_getfenv(L, -1); lua_remove(L, -2); if (proxy[i].table && !strcmp(proxy[i].table, "posix") && !strcmp(proxy[i].name, "execp")) { execp_handler = rl.handler[i]; rl.handler[i] = proxy_execp; } lua_pushinteger(L, i); lua_pushcclosure(L, proxy_handler, 1); lua_pushvalue(L, -2); lua_setfenv(L, -2); lua_setfield(L, n, proxy[i].name); if (proxy[i].table) lua_pop(L, 2); else lua_pop(L, 1); } if (len < 0) len = strlen(body); if (luaL_loadbuffer(L, body, len, name)) { fprintf(stderr, "failed to load lua script\n"); lua_close(L); return -1; } if (lua_pcall(L, 0, 0, 0)) { fprintf(stderr, "lua script failed: %s\n", lua_tostring(L, -1)); lua_pop(L, 1); lua_close(L); return -1; } lua_close(L); return 0; }