1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
1.2 +++ b/librazor/lua.c Fri Apr 17 21:09:55 2009 +0100
1.3 @@ -0,0 +1,361 @@
1.4 +/*
1.5 + * Copyright (C) 2009 J. Ali Harlow <ali@juiblex.co.uk>
1.6 + *
1.7 + * This program is free software; you can redistribute it and/or modify
1.8 + * it under the terms of the GNU General Public License as published by
1.9 + * the Free Software Foundation; either version 2 of the License, or
1.10 + * (at your option) any later version.
1.11 + *
1.12 + * This program is distributed in the hope that it will be useful,
1.13 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
1.14 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1.15 + * GNU General Public License for more details.
1.16 + *
1.17 + * You should have received a copy of the GNU General Public License along
1.18 + * with this program; if not, write to the Free Software Foundation, Inc.,
1.19 + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
1.20 + */
1.21 +
1.22 +#include "config.h"
1.23 +
1.24 +#include <stdlib.h>
1.25 +#include <string.h>
1.26 +#include <stdio.h>
1.27 +#include <errno.h>
1.28 +#include <sys/stat.h>
1.29 +#include <sys/types.h>
1.30 +#include <lua.h>
1.31 +#include <lualib.h>
1.32 +#include <lauxlib.h>
1.33 +
1.34 +#include "razor.h"
1.35 +#include "razor-internal.h"
1.36 +
1.37 +#define MAX_ARGS 2
1.38 +
1.39 +#define ARG_FLAG_OPT 0x1
1.40 +#define ARG_FLAG_BYPASS 0x2
1.41 +
1.42 +#define ARG(flags, swtch, index, type) \
1.43 + ((flags) << 26 | (swtch) << 21 | \
1.44 + (index) << 16 | type)
1.45 +#define ARG_FLAGS(arg) ((unsigned)(arg) >> 26)
1.46 +#define ARG_SWTCH(arg) ((unsigned)(arg) >> 21 & 0x1F)
1.47 +#define ARG_INDEX(arg) ((unsigned)(arg) >> 16 & 0x1F)
1.48 +#define ARG_TYPE(arg) ((unsigned)(arg) & 0xFFFF)
1.49 +
1.50 +#define ARG_NONE 0
1.51 +#define ARG_STRING(n) ARG(0, 0, n, LUA_TSTRING)
1.52 +#define ARG_OPT_STRING(n) ARG(ARG_FLAG_OPT, 0, n, LUA_TSTRING)
1.53 +#define ARG_BYP_STRING(n, s) ARG(ARG_FLAG_BYPASS, s, n, LUA_TSTRING)
1.54 +
1.55 +struct razor_proxy {
1.56 + char *table, *name;
1.57 + int result;
1.58 + unsigned args[MAX_ARGS];
1.59 +};
1.60 +
1.61 +static struct razor_proxy proxy[] = {
1.62 + { NULL, "dofile", LUA_TNONE, { ARG_STRING(1) } },
1.63 + { NULL, "loadfile", LUA_TNONE, { ARG_OPT_STRING(1) } },
1.64 + { "io", "input", LUA_TNONE, { ARG_OPT_STRING(1) } },
1.65 + { "io", "output", LUA_TNONE, { ARG_OPT_STRING(1) } },
1.66 + { "io", "lines", LUA_TNONE, { ARG_OPT_STRING(1) } },
1.67 + { "io", "open", LUA_TNONE, { ARG_STRING(1) } },
1.68 + { "io", "popen", LUA_TNONE, { ARG_STRING(1) } },
1.69 + /*
1.70 + * Note: We do not proxy os.execute
1.71 + * We can't do it properly (there is no way to distinguish filenames
1.72 + * in arguments from any other strings) so it's better not to try.
1.73 + * Scripts that attempt to be portable should use posix.exec[p]
1.74 + */
1.75 + { "os", "remove", LUA_TNONE, { ARG_STRING(1) } },
1.76 + { "os", "rename", LUA_TNONE, { ARG_STRING(1), ARG_STRING(2) } },
1.77 + /*
1.78 + * Note: We do not proxy os.tmpname
1.79 + * It should never be used. scripts should use io.tmpfile instead
1.80 + * if the rather limited functionality it provides suffices. The
1.81 + * proper solution is to implement posix.mkstemp
1.82 + */
1.83 + { "posix", "access", LUA_TNONE, { ARG_STRING(1) } },
1.84 + { "posix", "chdir", LUA_TNONE, { ARG_STRING(1) } },
1.85 + { "posix", "chmod", LUA_TNONE, { ARG_STRING(1) } },
1.86 + { "posix", "chown", LUA_TNONE, { ARG_STRING(1) } },
1.87 + { "posix", "dir", LUA_TNONE, { ARG_OPT_STRING(1) } },
1.88 + { "posix", "exec", LUA_TNONE, { ARG_STRING(1) } },
1.89 + { "posix", "execp", LUA_TNONE, { ARG_STRING(1) } },
1.90 + { "posix", "files", LUA_TNONE, { ARG_OPT_STRING(1) } },
1.91 + { "posix", "getcwd", LUA_TSTRING, { ARG_NONE } },
1.92 + { "posix", "glob", LUA_TTABLE, { ARG_STRING(1) } },
1.93 + { "posix", "link", LUA_TNONE, { ARG_BYP_STRING(1, 3), ARG_STRING(2) } },
1.94 + { "posix", "mkdir", LUA_TNONE, { ARG_STRING(1) } },
1.95 + { "posix", "mkfifo", LUA_TNONE, { ARG_STRING(1) } },
1.96 + { "posix", "pathconf", LUA_TNONE, { ARG_STRING(1) } },
1.97 + { "posix", "readlink", LUA_TNONE, { ARG_STRING(1) } },
1.98 + { "posix", "rmdir", LUA_TNONE, { ARG_STRING(1) } },
1.99 + { "posix", "stat", LUA_TNONE, { ARG_STRING(1) } },
1.100 + { "posix", "unlink", LUA_TNONE, { ARG_STRING(1) } },
1.101 + { "posix", "utime", LUA_TNONE, { ARG_STRING(1) } },
1.102 +};
1.103 +
1.104 +static lua_CFunction execp_handler;
1.105 +
1.106 +struct razor_lua {
1.107 + const char *root;
1.108 + lua_CFunction handler[ARRAY_SIZE(proxy)];
1.109 +};
1.110 +
1.111 +static void *alloc_lua(void *user_data, void *ptr, size_t osize, size_t nsize)
1.112 +{
1.113 + if (!nsize) {
1.114 + free(ptr);
1.115 + return NULL;
1.116 + } else
1.117 + return realloc(ptr, nsize);
1.118 +}
1.119 +
1.120 +static int proxy_execp(lua_State *L)
1.121 +{
1.122 + int r, n, err, got_eacces = 0;
1.123 + char *file = strdup(luaL_checkstring(L, 1));
1.124 + char *exe;
1.125 + const char *s, *e, *path;
1.126 + struct razor_lua *rl;
1.127 +
1.128 + if (*file == '\0' || strchr(file, '/')) {
1.129 + r = execp_handler(L);
1.130 + free(file);
1.131 + return r;
1.132 + }
1.133 +
1.134 + (void)lua_getallocf(L, (void **)&rl);
1.135 +
1.136 + path = getenv("PATH");
1.137 + if (!path)
1.138 + path = ":/bin:/usr/bin";
1.139 +
1.140 + s = path;
1.141 + for (;;) {
1.142 + e = strchr(s, ':');
1.143 + if (!e)
1.144 + e = s + strlen(s);
1.145 + if (*s == '/') {
1.146 + n = strlen(rl->root);
1.147 + exe = malloc(n + (e - s) + 1 + strlen(file) + 1);
1.148 + memcpy(exe, rl->root, n);
1.149 + memcpy(exe + n, s, e - s);
1.150 + n += e - s;
1.151 + exe[n++] = '/';
1.152 + strcpy(exe + n, file);
1.153 + } else if (e != s) {
1.154 + exe = malloc((e - s) + 1 + strlen(file) + 1);
1.155 + memcpy(exe, s, e - s);
1.156 + n = e - s;
1.157 + exe[n++] = '/';
1.158 + strcpy(exe + n, file);
1.159 + } else {
1.160 + /* Ensure exe contains a slash to avoid PATH search */
1.161 + exe = malloc(2 + strlen(file) + 1);
1.162 + exe[0] = '.';
1.163 + exe[1] = '/';
1.164 + strcpy(exe + 2, file);
1.165 + }
1.166 + lua_pushstring(L, exe);
1.167 + lua_replace(L, 1);
1.168 + free(exe);
1.169 + n = lua_gettop(L);
1.170 + r = execp_handler(L);
1.171 +
1.172 + err = lua_tointeger(L, -1);
1.173 + switch (err) {
1.174 + case EACCES:
1.175 + got_eacces = 1;
1.176 + /* Fall through */
1.177 + case ENOENT:
1.178 + case ESTALE:
1.179 + case ENOTDIR:
1.180 + case ENODEV:
1.181 + case ETIMEDOUT:
1.182 + lua_pop(L, lua_gettop(L) - n);
1.183 + break;
1.184 + default:
1.185 + free(file);
1.186 + return r;
1.187 + }
1.188 +
1.189 + if (*e)
1.190 + s = e + 1;
1.191 + else
1.192 + break;
1.193 + }
1.194 +
1.195 + if (got_eacces)
1.196 + err = EACCES;
1.197 +
1.198 + lua_pushnil(L);
1.199 + lua_pushfstring(L, "%s: %s", file, strerror(err));
1.200 + lua_pushinteger(L, err);
1.201 +
1.202 + free(file);
1.203 + return 3;
1.204 +}
1.205 +
1.206 +static int real2fake(lua_State *L, struct razor_lua *rl, int indx)
1.207 +{
1.208 + const char *path;
1.209 +
1.210 + if (indx < 0)
1.211 + indx += lua_gettop(L) + 1;
1.212 +
1.213 + if (lua_type(L, indx) == LUA_TSTRING) {
1.214 + path = lua_tostring(L, indx);
1.215 + if (path && !strncmp(path, rl->root, strlen(rl->root)) &&
1.216 + (path[strlen(rl->root)] == '/' || !path[strlen(rl->root)])) {
1.217 + lua_pushstring(L, path + strlen(rl->root));
1.218 + lua_replace(L, indx);
1.219 + return 1;
1.220 + }
1.221 + }
1.222 +
1.223 + return 0;
1.224 +}
1.225 +
1.226 +static int proxy_handler(lua_State *L)
1.227 +{
1.228 + int i, retval, cond;
1.229 + unsigned arg;
1.230 + const char *path;
1.231 + struct razor_lua *rl;
1.232 + int method = lua_tointeger(L, lua_upvalueindex(1));
1.233 + struct razor_proxy *rp = proxy + method;
1.234 +
1.235 + (void)lua_getallocf(L, (void **)&rl);
1.236 +
1.237 + for(i = 0; i < MAX_ARGS; i++) {
1.238 + arg = rp->args[i];
1.239 + if (arg == ARG_NONE)
1.240 + continue;
1.241 + switch (ARG_TYPE(arg)) {
1.242 + case LUA_TSTRING:
1.243 + if (ARG_FLAGS(arg) & ARG_FLAG_BYPASS)
1.244 + cond = !lua_toboolean(L, ARG_SWTCH(arg));
1.245 + else
1.246 + cond = 1;
1.247 + if (ARG_FLAGS(arg) & ARG_FLAG_OPT) {
1.248 + if (lua_isstring(L, ARG_INDEX(arg)))
1.249 + path = lua_tostring(L, ARG_INDEX(arg));
1.250 + else
1.251 + path = NULL;
1.252 + } else
1.253 + path = luaL_checkstring(L, ARG_INDEX(arg));
1.254 + if (cond && path && *path == '/') {
1.255 + lua_pushfstring(L, "%s%s", rl->root, path);
1.256 + lua_replace(L, ARG_INDEX(arg));
1.257 + }
1.258 + break;
1.259 + default:
1.260 + lua_pushfstring(L, "razor proxy: Unhandled type (%d)",
1.261 + ARG_TYPE(arg));
1.262 + lua_error(L);
1.263 + }
1.264 + }
1.265 +
1.266 + retval = rl->handler[method](L);
1.267 +
1.268 + if (rp->result != LUA_TNONE && !lua_isnil(L, 1)) {
1.269 + switch (rp->result) {
1.270 + case LUA_TNONE:
1.271 + break;
1.272 + case LUA_TSTRING:
1.273 + real2fake(L, rl, -1);
1.274 + break;
1.275 + case LUA_TTABLE:
1.276 + lua_pushnil(L);
1.277 + while (lua_next(L, -2)) {
1.278 + if (real2fake(L, rl, -1)) {
1.279 + i = lua_tonumber(L, -2);
1.280 + lua_rawseti(L, -3, i);
1.281 + } else
1.282 + lua_pop(L, 1);
1.283 + }
1.284 + break;
1.285 + default:
1.286 + lua_pushfstring(L, "razor proxy: Unhandled type (%d)",
1.287 + rp->result);
1.288 + lua_error(L);
1.289 + }
1.290 + }
1.291 +
1.292 + return retval;
1.293 +}
1.294 +
1.295 +int run_lua_script(const char *root, const char *name, const char *body,
1.296 + ssize_t len)
1.297 +{
1.298 + int i, n;
1.299 + lua_State *L;
1.300 + struct razor_lua rl;
1.301 +
1.302 + if (root && strcmp(root, "/"))
1.303 + rl.root = root;
1.304 + else
1.305 + rl.root = NULL;
1.306 +
1.307 + L = lua_newstate(alloc_lua, &rl);
1.308 + luaL_openlibs(L);
1.309 + lua_getglobal(L, "require");
1.310 + lua_pushstring(L, "posix");
1.311 + if (lua_pcall(L, 1, 1, 0)) {
1.312 + fprintf(stderr, "lua posix: %s\n", lua_tostring(L, -1));
1.313 + lua_pop(L, 1);
1.314 + lua_close(L);
1.315 + return -1;
1.316 + }
1.317 +
1.318 + if (rl.root)
1.319 + for(i = 0; i < ARRAY_SIZE(proxy); i++) {
1.320 + if (proxy[i].table) {
1.321 + lua_getglobal(L, proxy[i].table);
1.322 + n = lua_gettop(L);
1.323 + } else
1.324 + n = LUA_GLOBALSINDEX;
1.325 + lua_getfield(L, n, proxy[i].name);
1.326 + rl.handler[i] = lua_tocfunction(L, -1);
1.327 + lua_getfenv(L, -1);
1.328 + lua_remove(L, -2);
1.329 + if (proxy[i].table &&
1.330 + !strcmp(proxy[i].table, "posix") &&
1.331 + !strcmp(proxy[i].name, "execp")) {
1.332 + execp_handler = rl.handler[i];
1.333 + rl.handler[i] = proxy_execp;
1.334 + }
1.335 + lua_pushinteger(L, i);
1.336 + lua_pushcclosure(L, proxy_handler, 1);
1.337 + lua_pushvalue(L, -2);
1.338 + lua_setfenv(L, -2);
1.339 + lua_setfield(L, n, proxy[i].name);
1.340 + if (proxy[i].table)
1.341 + lua_pop(L, 2);
1.342 + else
1.343 + lua_pop(L, 1);
1.344 + }
1.345 +
1.346 + if (len < 0)
1.347 + len = strlen(body);
1.348 +
1.349 + if (luaL_loadbuffer(L, body, len, name)) {
1.350 + fprintf(stderr, "failed to load lua script\n");
1.351 + lua_close(L);
1.352 + return -1;
1.353 + }
1.354 +
1.355 + if (lua_pcall(L, 0, 0, 0)) {
1.356 + fprintf(stderr, "lua script failed: %s\n", lua_tostring(L, -1));
1.357 + lua_pop(L, 1);
1.358 + lua_close(L);
1.359 + return -1;
1.360 + }
1.361 +
1.362 + lua_close(L);
1.363 + return 0;
1.364 +}