--- /dev/null
+/*
+ * Copyright (C) 2009 J. Ali Harlow <ali@juiblex.co.uk>
+ *
+ * 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 <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <errno.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <lua.h>
+#include <lualib.h>
+#include <lauxlib.h>
+
+#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;
+}
+
+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);
+ 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;
+}
--- /dev/null
+/*
+ * Copyright (C) 2009 J. Ali Harlow <ali@juiblex.co.uk>
+ *
+ * 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 <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <dirent.h>
+#include <unistd.h>
+#include <math.h>
+#include "razor-internal.h"
+
+/*
+ * This kludge is to work around an apparent bug in Fedora 10's lua package
+ * (it appears to require libm but not to include a DT_NEEDED).
+ */
+
+void (*kludge)() = (void (*)())sin;
+
+static void recursive_remove(const char *directory)
+{
+ DIR *dp;
+ struct dirent *dirp;
+ char *buf;
+
+ dp = opendir(directory);
+ while((dirp = readdir(dp))) {
+ if (strcmp(dirp->d_name, ".") && strcmp(dirp->d_name, "..")) {
+ buf = malloc(strlen(directory) + strlen(dirp->d_name)
+ + 2);
+ sprintf(buf, "%s/%s", directory, dirp->d_name);
+ if (remove(buf) < 0)
+ recursive_remove(buf);
+ free(buf);
+ }
+ }
+
+ rmdir(directory);
+}
+
+int main(int argc, char *argv[])
+{
+ char root[] = "/tmp/razor.XXXXXX";
+ int r;
+ void *script;
+ size_t len;
+ char *s, *test_file;
+ FILE *fp;
+
+ if (argc > 2) {
+ fprintf(stderr, "usage: %s [TESTS-FILE]\n", argv[0]);
+ exit(1);
+ }
+ if (argc == 2)
+ test_file = argv[1];
+ else
+ test_file = "test.lua";
+
+ if (!mkdtemp(root)) {
+ perror(root);
+ exit(1);
+ }
+
+ s = malloc(strlen(root) + strlen("/testfile") + 1);
+ strcpy(s, root);
+ strcat(s, "/testfile");
+ fp = fopen(s, "w");
+ if (!fp) {
+ perror(s);
+ exit(1);
+ }
+ fprintf(fp, "#!" LUA_BINARY "\n"
+ "print('Abracadabra!')\n");
+ fchmod(fileno(fp), S_IRUSR | S_IWUSR | S_IXUSR);
+ fclose(fp);
+ free(s);
+
+ script = razor_file_get_contents(test_file, &len);
+ r = run_lua_script(root, test_file, script, len);
+ razor_file_free_contents(script, len);
+
+ recursive_remove(root);
+ exit(r ? 1 : 0);
+}