librazor/lua.c
changeset 357 39f8dab73084
child 361 2523d03a840e
     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 +}