aboutsummaryrefslogtreecommitdiff
Commit message (Expand)AuthorAge
...
* | | | Translated using Weblate (Russian)Mitori Itoshiki2013-02-05
* | | | Translated using Weblate (French)we prefer instagib metl32013-02-05
* | | | Merge remote branch 'origin/master'Weblate2013-02-05
|\| | |
| * | | Add Japanese & Korean languages.Ilya Zhuravlev2013-02-05
* | | | Translated using Weblate (Spanish)Marco gonzalez2013-02-05
* | | | Translated using Weblate (Russian)Ilya Zhuravlev2013-02-05
* | | | Translated using Weblate (German)Pilz Adam2013-02-05
* | | | Translated using Weblate (Chinese (China))L JJ2013-02-05
* | | | Translated using Weblate (Chinese (China))Shen Zheyu2013-02-05
* | | | Translated using Weblate (Spanish)Lord James2013-02-04
* | | | Translated using Weblate (German)Pilz Adam2013-02-02
|/ / /
* | | Merge remote branch 'origin/master'Weblate2013-02-02
|\ \ \
| * | | Add Chinese Simplified & Portuguese.Ilya Zhuravlev2013-02-02
| * | | Translate key functions in key change menuPilzAdam2013-01-30
| |/ /
| * | Merge pull request #442 from kwolekr/mingw_compile_fixkwolekr2013-01-29
| |\ \
| | * | Slightly improved version of mystrtok_rkwolekr2013-01-23
| | * | Fix compile under MingWkwolekr2013-01-23
| * | | Merge pull request #441 from kwolekr/mapgen_factorieskwolekr2013-01-29
| |\ \ \
| | * | | Make mapgen factory setup more elegant, add mapgen_v6.hkwolekr2013-01-23
| | |/ /
* | | | Translated using Weblate (Spanish)Lord James2013-02-02
* | | | Translated using Weblate (Russian)Pavel Elagin2013-02-02
* | | | Translated using Weblate (Polish)Maciej Kasatkin2013-02-02
* | | | Translated using Weblate (Italian)Robert Arkenin2013-02-02
* | | | Translated using Weblate (German)Pilz Adam2013-02-02
* | | | Translated using Weblate (French)sub reptice2013-02-02
* | | | Translated using Weblate (Spanish)Marco gonzalez2013-02-01
* | | | Translated using Weblate (Spanish)Diego Martínez2013-01-31
* | | | Translated using Weblate (Spanish)Marco gonzalez2013-01-31
* | | | Translated using Weblate (Polish)Maciej Kasatkin2013-01-30
* | | | Translated using Weblate (Russian)Robert Arkenin2013-01-29
* | | | Translated using Weblate (Russian)Pavel Elagin2013-01-29
* | | | Translated using Weblate (Spanish)Marco gonzalez2013-01-29
* | | | Translated using Weblate (Spanish)Diego Martínez2013-01-29
* | | | Translated using Weblate (Russian)Ilya Zhuravlev2013-01-29
* | | | Translated using Weblate (Spanish)Marco gonzalez2013-01-29
* | | | Translated using Weblate (Russian)Pavel Elagin2013-01-29
|/ / /
* | | Add Spanish, Russian, Polish and Romanian languages.Ilya Zhuravlev2013-01-29
* | | Dont call on_rightclick() if sneak is pressedPilzAdam2013-01-27
* | | Place block when holding sneak while right-clicking nodes with formspecJeija2013-01-28
* | | Workaround failing Travis clang build.Ilya Zhuravlev2013-01-28
* | | Merge pull request #447 from sapier/add_lua_log_parameter_checkkwolekr2013-01-26
|\ \ \
| * | | check parameters for minetest.log lua functionsapier2013-01-26
| | |/ | |/|
* | | Merge remote branch 'origin/master'Weblate2013-01-24
|\ \ \ | | |/ | |/|
| * | Tweak buildbotsfan52013-01-23
| * | Treegen update. Some new symbols. Speed up code a bit.RealBadAngel2013-01-23
| * | Merge pull request #436 from doserj/mod_selectionceleron552013-01-23
| |\ \
| | * | Make sure that settings are written to config file when settings are removed.Jürgen Doser2013-01-22
| | * | Fix crash when pressing delete button in server browser and no server is sele...Jürgen Doser2013-01-22
| | * | Improve behaviour for empty modpacks and when no mods at all are installed:Jürgen Doser2013-01-22
| | * | Fix crash when no world is selected and configure button is pressed.Jürgen Doser2013-01-22
'#n620'>620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821
/*
Minetest
Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.

You should have received a copy of the GNU Lesser 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 "cpp_api/s_security.h"

#include "filesys.h"
#include "porting.h"
#include "server.h"
#include "client/client.h"
#include "settings.h"

#include <cerrno>
#include <string>
#include <iostream>


#define SECURE_API(lib, name) \
	lua_pushcfunction(L, sl_##lib##_##name); \
	lua_setfield(L, -2, #name);


static inline void copy_safe(lua_State *L, const char *list[], unsigned len, int from=-2, int to=-1)
{
	if (from < 0) from = lua_gettop(L) + from + 1;
	if (to   < 0) to   = lua_gettop(L) + to   + 1;
	for (unsigned i = 0; i < (len / sizeof(list[0])); i++) {
		lua_getfield(L, from, list[i]);
		lua_setfield(L, to,   list[i]);
	}
}

// Pushes the original version of a library function on the stack, from the old version
static inline void push_original(lua_State *L, const char *lib, const char *func)
{
	lua_rawgeti(L, LUA_REGISTRYINDEX, CUSTOM_RIDX_GLOBALS_BACKUP);
	lua_getfield(L, -1, lib);
	lua_remove(L, -2);  // Remove globals_backup
	lua_getfield(L, -1, func);
	lua_remove(L, -2);  // Remove lib
}


void ScriptApiSecurity::initializeSecurity()
{
	static const char *whitelist[] = {
		"assert",
		"core",
		"collectgarbage",
		"DIR_DELIM",
		"error",
		"getfenv",
		"getmetatable",
		"ipairs",
		"next",
		"pairs",
		"pcall",
		"print",
		"rawequal",
		"rawget",
		"rawset",
		"select",
		"setfenv",
		"setmetatable",
		"tonumber",
		"tostring",
		"type",
		"unpack",
		"_VERSION",
		"xpcall",
		// Completely safe libraries
		"coroutine",
		"string",
		"table",
		"math",
	};
	static const char *io_whitelist[] = {
		"close",
		"flush",
		"read",
		"type",
		"write",
	};
	static const char *os_whitelist[] = {
		"clock",
		"date",
		"difftime",
		"getenv",
		"setlocale",
		"time",
		"tmpname",
	};
	static const char *debug_whitelist[] = {
		"gethook",
		"traceback",
		"getinfo",
		"getmetatable",
		"setupvalue",
		"setmetatable",
		"upvalueid",
		"sethook",
		"debug",
		"setlocal",
	};
	static const char *package_whitelist[] = {
		"config",
		"cpath",
		"path",
		"searchpath",
	};
#if USE_LUAJIT
	static const char *jit_whitelist[] = {
		"arch",
		"flush",
		"off",
		"on",
		"opt",
		"os",
		"status",
		"version",
		"version_num",
	};
#endif
	m_secure = true;

	lua_State *L = getStack();

	// Backup globals to the registry
	lua_getglobal(L, "_G");
	lua_rawseti(L, LUA_REGISTRYINDEX, CUSTOM_RIDX_GLOBALS_BACKUP);

	// Replace the global environment with an empty one
	int thread = getThread(L);
	createEmptyEnv(L);
	setLuaEnv(L, thread);

	// Get old globals
	lua_rawgeti(L, LUA_REGISTRYINDEX, CUSTOM_RIDX_GLOBALS_BACKUP);
	int old_globals = lua_gettop(L);


	// Copy safe base functions
	lua_getglobal(L, "_G");
	copy_safe(L, whitelist, sizeof(whitelist));

	// And replace unsafe ones
	SECURE_API(g, dofile);
	SECURE_API(g, load);
	SECURE_API(g, loadfile);
	SECURE_API(g, loadstring);
	SECURE_API(g, require);
	lua_pop(L, 1);


	// Copy safe IO functions
	lua_getfield(L, old_globals, "io");
	lua_newtable(L);
	copy_safe(L, io_whitelist, sizeof(io_whitelist));

	// And replace unsafe ones
	SECURE_API(io, open);
	SECURE_API(io, input);
	SECURE_API(io, output);
	SECURE_API(io, lines);

	lua_setglobal(L, "io");
	lua_pop(L, 1);  // Pop old IO


	// Copy safe OS functions
	lua_getfield(L, old_globals, "os");
	lua_newtable(L);
	copy_safe(L, os_whitelist, sizeof(os_whitelist));

	// And replace unsafe ones
	SECURE_API(os, remove);
	SECURE_API(os, rename);

	lua_setglobal(L, "os");
	lua_pop(L, 1);  // Pop old OS


	// Copy safe debug functions
	lua_getfield(L, old_globals, "debug");
	lua_newtable(L);
	copy_safe(L, debug_whitelist, sizeof(debug_whitelist));
	lua_setglobal(L, "debug");
	lua_pop(L, 1);  // Pop old debug


	// Copy safe package fields
	lua_getfield(L, old_globals, "package");
	lua_newtable(L);
	copy_safe(L, package_whitelist, sizeof(package_whitelist));
	lua_setglobal(L, "package");
	lua_pop(L, 1);  // Pop old package

#if USE_LUAJIT
	// Copy safe jit functions, if they exist
	lua_getfield(L, -1, "jit");
	if (!lua_isnil(L, -1)) {
		lua_newtable(L);
		copy_safe(L, jit_whitelist, sizeof(jit_whitelist));
		lua_setglobal(L, "jit");
	}
	lua_pop(L, 1);  // Pop old jit
#endif

	lua_pop(L, 1); // Pop globals_backup
}

void ScriptApiSecurity::initializeSecurityClient()
{
	static const char *whitelist[] = {
		"assert",
		"core",
		"collectgarbage",
		"DIR_DELIM",
		"error",
		"getfenv",
		"ipairs",
		"next",
		"pairs",
		"pcall",
		"print",
		"rawequal",
		"rawget",
		"rawset",
		"select",
		"setfenv",
		// getmetatable can be used to escape the sandbox
		"setmetatable",
		"tonumber",
		"tostring",
		"type",
		"unpack",
		"_VERSION",
		"xpcall",
		// Completely safe libraries
		"coroutine",
		"string",
		"table",
		"math",
	};
	static const char *os_whitelist[] = {
		"clock",
		"date",
		"difftime",
		"time"
	};
	static const char *debug_whitelist[] = {
		"getinfo",
		"traceback"
	};

#if USE_LUAJIT
	static const char *jit_whitelist[] = {
		"arch",
		"flush",
		"off",
		"on",
		"opt",
		"os",
		"status",
		"version",
		"version_num",
	};
#endif

	m_secure = true;

	lua_State *L = getStack();
	int thread = getThread(L);

	// create an empty environment
	createEmptyEnv(L);

	// Copy safe base functions
	lua_getglobal(L, "_G");
	lua_getfield(L, -2, "_G");
	copy_safe(L, whitelist, sizeof(whitelist));

	// And replace unsafe ones
	SECURE_API(g, dofile);
	SECURE_API(g, load);
	SECURE_API(g, loadfile);
	SECURE_API(g, loadstring);
	SECURE_API(g, require);
	lua_pop(L, 2);



	// Copy safe OS functions
	lua_getglobal(L, "os");
	lua_newtable(L);
	copy_safe(L, os_whitelist, sizeof(os_whitelist));
	lua_setfield(L, -3, "os");
	lua_pop(L, 1);  // Pop old OS


	// Copy safe debug functions
	lua_getglobal(L, "debug");
	lua_newtable(L);
	copy_safe(L, debug_whitelist, sizeof(debug_whitelist));
	lua_setfield(L, -3, "debug");
	lua_pop(L, 1);  // Pop old debug

#if USE_LUAJIT
	// Copy safe jit functions, if they exist
	lua_getglobal(L, "jit");
	lua_newtable(L);
	copy_safe(L, jit_whitelist, sizeof(jit_whitelist));
	lua_setfield(L, -3, "jit");
	lua_pop(L, 1);  // Pop old jit
#endif

	// Set the environment to the one we created earlier
	setLuaEnv(L, thread);
}

int ScriptApiSecurity::getThread(lua_State *L)
{
#if LUA_VERSION_NUM <= 501
	int is_main = lua_pushthread(L);  // Push the main thread
	FATAL_ERROR_IF(!is_main, "Security: ScriptApi's Lua state "
		"isn't the main Lua thread!");
	return lua_gettop(L);
#endif
	return 0;
}

void ScriptApiSecurity::createEmptyEnv(lua_State *L)
{
	lua_newtable(L);  // Create new environment
	lua_pushvalue(L, -1);
	lua_setfield(L, -2, "_G");  // Create the _G loop
}

void ScriptApiSecurity::setLuaEnv(lua_State *L, int thread)
{
#if LUA_VERSION_NUM >= 502  // Lua >= 5.2
	// Set the global environment
	lua_rawseti(L, LUA_REGISTRYINDEX, LUA_RIDX_GLOBALS);
#else  // Lua <= 5.1
	// Set the environment of the main thread
	FATAL_ERROR_IF(!lua_setfenv(L, thread), "Security: Unable to set "
		"environment of the main Lua thread!");
	lua_pop(L, 1);  // Pop thread
#endif
}

bool ScriptApiSecurity::isSecure(lua_State *L)
{
	lua_rawgeti(L, LUA_REGISTRYINDEX, CUSTOM_RIDX_GLOBALS_BACKUP);
	bool secure = !lua_isnil(L, -1);
	lua_pop(L, 1);
	return secure;
}


#define CHECK_FILE_ERR(ret, fp) \
	if (ret) { \
		lua_pushfstring(L, "%s: %s", path, strerror(errno)); \
		if (fp) std::fclose(fp); \
		return false; \
	}


bool ScriptApiSecurity::safeLoadFile(lua_State *L, const char *path, const char *display_name)
{
	FILE *fp;
	char *chunk_name;
	if (!display_name)
		display_name = path;
	if (!path) {
		fp = stdin;
		chunk_name = const_cast<char *>("=stdin");
	} else {
		fp = fopen(path, "rb");
		if (!fp) {
			lua_pushfstring(L, "%s: %s", path, strerror(errno));
			return false;
		}
		chunk_name = new char[strlen(display_name) + 2];
		chunk_name[0] = '@';
		chunk_name[1] = '\0';
		strcat(chunk_name, display_name);
	}

	size_t start = 0;
	int c = std::getc(fp);
	if (c == '#') {
		// Skip the first line
		while ((c = std::getc(fp)) != EOF && c != '\n');
		if (c == '\n') c = std::getc(fp);
		start = std::ftell(fp);
	}

	if (c == LUA_SIGNATURE[0]) {
		lua_pushliteral(L, "Bytecode prohibited when mod security is enabled.");
		std::fclose(fp);
		if (path) {
			delete [] chunk_name;
		}
		return false;
	}

	// Read the file
	int ret = std::fseek(fp, 0, SEEK_END);
	if (ret) {
		lua_pushfstring(L, "%s: %s", path, strerror(errno));
		std::fclose(fp);
		if (path) {
			delete [] chunk_name;
		}
		return false;
	}

	size_t size = std::ftell(fp) - start;
	char *code = new char[size];
	ret = std::fseek(fp, start, SEEK_SET);
	if (ret) {
		lua_pushfstring(L, "%s: %s", path, strerror(errno));
		std::fclose(fp);
		delete [] code;
		if (path) {
			delete [] chunk_name;
		}
		return false;
	}

	size_t num_read = std::fread(code, 1, size, fp);
	if (path) {
		std::fclose(fp);
	}
	if (num_read != size) {
		lua_pushliteral(L, "Error reading file to load.");
		delete [] code;
		if (path) {
			delete [] chunk_name;
		}
		return false;
	}

	if (luaL_loadbuffer(L, code, size, chunk_name)) {
		delete [] code;
		return false;
	}

	delete [] code;

	if (path) {
		delete [] chunk_name;
	}
	return true;
}


bool ScriptApiSecurity::checkPath(lua_State *L, const char *path,
		bool write_required, bool *write_allowed)
{
	if (write_allowed)
		*write_allowed = false;

	std::string str;  // Transient

	std::string abs_path = fs::AbsolutePath(path);

	if (!abs_path.empty()) {
		// Don't allow accessing the settings file
		str = fs::AbsolutePath(g_settings_path);
		if (str == abs_path) return false;
	}

	// If we couldn't find the absolute path (path doesn't exist) then
	// try removing the last components until it works (to allow
	// non-existent files/folders for mkdir).
	std::string cur_path = path;
	std::string removed;
	while (abs_path.empty() && !cur_path.empty()) {
		std::string component;
		cur_path = fs::RemoveLastPathComponent(cur_path, &component);
		if (component == "..") {
			// Parent components can't be allowed or we could allow something like
			// /home/user/minetest/worlds/foo/noexist/../../../../../../etc/passwd.
			// If we have previous non-relative elements in the path we might be
			// able to remove them so that things like worlds/foo/noexist/../auth.txt
			// could be allowed, but those paths will be interpreted as nonexistent
			// by the operating system anyways.
			return false;
		}
		removed.append(component).append(removed.empty() ? "" : DIR_DELIM + removed);
		abs_path = fs::AbsolutePath(cur_path);
	}
	if (abs_path.empty())
		return false;
	// Add the removed parts back so that you can't, eg, create a
	// directory in worldmods if worldmods doesn't exist.
	if (!removed.empty())
		abs_path += DIR_DELIM + removed;

	// Get server from registry
	lua_rawgeti(L, LUA_REGISTRYINDEX, CUSTOM_RIDX_SCRIPTAPI);
	ScriptApiBase *script = (ScriptApiBase *) lua_touserdata(L, -1);
	lua_pop(L, 1);
	const IGameDef *gamedef = script->getGameDef();
	if (!gamedef)
		return false;

	// Get mod name
	lua_rawgeti(L, LUA_REGISTRYINDEX, CUSTOM_RIDX_CURRENT_MOD_NAME);
	if (lua_isstring(L, -1)) {
		std::string mod_name = readParam<std::string>(L, -1);

		// Builtin can access anything
		if (mod_name == BUILTIN_MOD_NAME) {
			if (write_allowed) *write_allowed = true;
			return true;
		}

		// Allow paths in mod path
		// Don't bother if write access isn't important, since it will be handled later
		if (write_required || write_allowed != NULL) {
			const ModSpec *mod = gamedef->getModSpec(mod_name);
			if (mod) {
				str = fs::AbsolutePath(mod->path);
				if (!str.empty() && fs::PathStartsWith(abs_path, str)) {
					if (write_allowed) *write_allowed = true;
					return true;
				}
			}
		}
	}
	lua_pop(L, 1);  // Pop mod name

	// Allow read-only access to all mod directories
	if (!write_required) {
		const std::vector<ModSpec> &mods = gamedef->getMods();
		for (const ModSpec &mod : mods) {
			str = fs::AbsolutePath(mod.path);
			if (!str.empty() && fs::PathStartsWith(abs_path, str)) {
				return true;
			}
		}
	}

	str = fs::AbsolutePath(gamedef->getWorldPath());
	if (!str.empty()) {
		// Don't allow access to other paths in the world mod/game path.
		// These have to be blocked so you can't override a trusted mod
		// by creating a mod with the same name in a world mod directory.
		// We add to the absolute path of the world instead of getting
		// the absolute paths directly because that won't work if they
		// don't exist.
		if (fs::PathStartsWith(abs_path, str + DIR_DELIM + "worldmods") ||
				fs::PathStartsWith(abs_path, str + DIR_DELIM + "game")) {
			return false;
		}
		// Allow all other paths in world path
		if (fs::PathStartsWith(abs_path, str)) {
			if (write_allowed) *write_allowed = true;
			return true;
		}
	}

	// Default to disallowing
	return false;
}


int ScriptApiSecurity::sl_g_dofile(lua_State *L)
{
	int nret = sl_g_loadfile(L);
	if (nret != 1) {
		lua_error(L);
		// code after this function isn't executed
	}
	int top_precall = lua_gettop(L);
	lua_call(L, 0, LUA_MULTRET);
	// Return number of arguments returned by the function,
	// adjusting for the function being poped.
	return lua_gettop(L) - (top_precall - 1);
}


int ScriptApiSecurity::sl_g_load(lua_State *L)
{
	size_t len;
	const char *buf;
	std::string code;
	const char *chunk_name = "=(load)";

	luaL_checktype(L, 1, LUA_TFUNCTION);
	if (!lua_isnone(L, 2)) {
		luaL_checktype(L, 2, LUA_TSTRING);
		chunk_name = lua_tostring(L, 2);
	}

	while (true) {
		lua_pushvalue(L, 1);
		lua_call(L, 0, 1);
		int t = lua_type(L, -1);
		if (t == LUA_TNIL) {
			break;
		}

		if (t != LUA_TSTRING) {
			lua_pushnil(L);
			lua_pushliteral(L, "Loader didn't return a string");
			return 2;
		}
		buf = lua_tolstring(L, -1, &len);
		code += std::string(buf, len);
		lua_pop(L, 1); // Pop return value
	}
	if (code[0] == LUA_SIGNATURE[0]) {
		lua_pushnil(L);
		lua_pushliteral(L, "Bytecode prohibited when mod security is enabled.");
		return 2;
	}
	if (luaL_loadbuffer(L, code.data(), code.size(), chunk_name)) {
		lua_pushnil(L);
		lua_insert(L, lua_gettop(L) - 1);
		return 2;
	}
	return 1;
}


int ScriptApiSecurity::sl_g_loadfile(lua_State *L)
{
#ifndef SERVER
	lua_rawgeti(L, LUA_REGISTRYINDEX, CUSTOM_RIDX_SCRIPTAPI);
	ScriptApiBase *script = (ScriptApiBase *) lua_touserdata(L, -1);
	lua_pop(L, 1);

	if (script->getType() == ScriptingType::Client) {
		std::string display_path = readParam<std::string>(L, 1);
		const std::string *path = script->getClient()->getModFile(display_path);
		if (!path) {
			std::string error_msg = "Coudln't find script called:" + display_path;
			lua_pushnil(L);
			lua_pushstring(L, error_msg.c_str());
			return 2;
		}
		if (!safeLoadFile(L, path->c_str(), display_path.c_str())) {
			lua_pushnil(L);
			lua_insert(L, -2);
			return 2;
		}
		return 1;
	}
#endif
	const char *path = NULL;
	if (lua_isstring(L, 1)) {
		path = lua_tostring(L, 1);
		CHECK_SECURE_PATH_INTERNAL(L, path, false, NULL);
	}

	if (!safeLoadFile(L, path)) {
		lua_pushnil(L);
		lua_insert(L, -2);
		return 2;
	}

	return 1;
}


int ScriptApiSecurity::sl_g_loadstring(lua_State *L)
{
	const char *chunk_name = "=(load)";

	luaL_checktype(L, 1, LUA_TSTRING);
	if (!lua_isnone(L, 2)) {
		luaL_checktype(L, 2, LUA_TSTRING);
		chunk_name = lua_tostring(L, 2);
	}

	size_t size;
	const char *code = lua_tolstring(L, 1, &size);

	if (size > 0 && code[0] == LUA_SIGNATURE[0]) {
		lua_pushnil(L);
		lua_pushliteral(L, "Bytecode prohibited when mod security is enabled.");
		return 2;
	}
	if (luaL_loadbuffer(L, code, size, chunk_name)) {
		lua_pushnil(L);
		lua_insert(L, lua_gettop(L) - 1);
		return 2;
	}
	return 1;
}


int ScriptApiSecurity::sl_g_require(lua_State *L)
{
	lua_pushliteral(L, "require() is disabled when mod security is on.");
	return lua_error(L);
}


int ScriptApiSecurity::sl_io_open(lua_State *L)
{
	bool with_mode = lua_gettop(L) > 1;

	luaL_checktype(L, 1, LUA_TSTRING);
	const char *path = lua_tostring(L, 1);

	bool write_requested = false;
	if (with_mode) {
		luaL_checktype(L, 2, LUA_TSTRING);
		const char *mode = lua_tostring(L, 2);
		write_requested = strchr(mode, 'w') != NULL ||
			strchr(mode, '+') != NULL ||
			strchr(mode, 'a') != NULL;
	}
	CHECK_SECURE_PATH_INTERNAL(L, path, write_requested, NULL);

	push_original(L, "io", "open");
	lua_pushvalue(L, 1);
	if (with_mode) {
		lua_pushvalue(L, 2);
	}

	lua_call(L, with_mode ? 2 : 1, 2);
	return 2;
}


int ScriptApiSecurity::sl_io_input(lua_State *L)
{
	if (lua_isstring(L, 1)) {
		const char *path = lua_tostring(L, 1);
		CHECK_SECURE_PATH_INTERNAL(L, path, false, NULL);
	}

	push_original(L, "io", "input");
	lua_pushvalue(L, 1);
	lua_call(L, 1, 1);
	return 1;
}


int ScriptApiSecurity::sl_io_output(lua_State *L)
{
	if (lua_isstring(L, 1)) {
		const char *path = lua_tostring(L, 1);
		CHECK_SECURE_PATH_INTERNAL(L, path, true, NULL);
	}

	push_original(L, "io", "output");
	lua_pushvalue(L, 1);
	lua_call(L, 1, 1);
	return 1;
}


int ScriptApiSecurity::sl_io_lines(lua_State *L)
{
	if (lua_isstring(L, 1)) {
		const char *path = lua_tostring(L, 1);
		CHECK_SECURE_PATH_INTERNAL(L, path, false, NULL);
	}

	int top_precall = lua_gettop(L);
	push_original(L, "io", "lines");
	lua_pushvalue(L, 1);
	lua_call(L, 1, LUA_MULTRET);
	// Return number of arguments returned by the function,
	// adjusting for the function being poped.
	return lua_gettop(L) - top_precall;
}


int ScriptApiSecurity::sl_os_rename(lua_State *L)
{
	luaL_checktype(L, 1, LUA_TSTRING);
	const char *path1 = lua_tostring(L, 1);
	CHECK_SECURE_PATH_INTERNAL(L, path1, true, NULL);

	luaL_checktype(L, 2, LUA_TSTRING);
	const char *path2 = lua_tostring(L, 2);
	CHECK_SECURE_PATH_INTERNAL(L, path2, true, NULL);

	push_original(L, "os", "rename");
	lua_pushvalue(L, 1);
	lua_pushvalue(L, 2);
	lua_call(L, 2, 2);
	return 2;
}


int ScriptApiSecurity::sl_os_remove(lua_State *L)
{
	luaL_checktype(L, 1, LUA_TSTRING);
	const char *path = lua_tostring(L, 1);
	CHECK_SECURE_PATH_INTERNAL(L, path, true, NULL);

	push_original(L, "os", "remove");
	lua_pushvalue(L, 1);
	lua_call(L, 1, 2);
	return 2;
}