changeset 1330:f41919741100

fix security
author Franklin Schmidt <fschmidt@gmail.com>
date Mon, 11 Feb 2019 01:38:55 -0700
parents 5a39b006acd1
children 02fe660e7748
files conv.txt src/luan/Luan.java src/luan/LuanClosure.java src/luan/LuanException.java src/luan/LuanJavaOk.java src/luan/LuanState.java src/luan/LuanTable.java src/luan/host/Backup.java src/luan/host/Init.luan src/luan/host/WebHandler.java src/luan/host/init.luan src/luan/impl/Closure.java src/luan/impl/LuanCompiler.java src/luan/impl/LuanImpl.java src/luan/impl/LuanParser.java src/luan/modules/BasicLuan.java src/luan/modules/Boot.luan src/luan/modules/Io.luan src/luan/modules/IoLuan.java src/luan/modules/JavaLuan.java src/luan/modules/PackageLuan.java src/luan/modules/TableLuan.java src/luan/modules/ThreadLuan.java src/luan/modules/lucene/Lucene.luan src/luan/modules/parsers/Css.java src/luan/modules/parsers/Csv.java src/luan/modules/parsers/Html.java
diffstat 27 files changed, 367 insertions(+), 322 deletions(-) [+]
line wrap: on
line diff
diff -r 5a39b006acd1 -r f41919741100 conv.txt
--- a/conv.txt	Sun Feb 10 02:01:49 2019 -0700
+++ b/conv.txt	Mon Feb 11 01:38:55 2019 -0700
@@ -1,3 +1,5 @@
+file - java.*
+Io.unrestricted
 Thread.global_callable
 Thread.global
 Logging.luanhost_logger
diff -r 5a39b006acd1 -r f41919741100 src/luan/Luan.java
--- a/src/luan/Luan.java	Sun Feb 10 02:01:49 2019 -0700
+++ b/src/luan/Luan.java	Mon Feb 11 01:38:55 2019 -0700
@@ -202,5 +202,34 @@
 	}
 */
 
+
+
+	// security
+
+	public interface Security {
+		public void check(LuanState luan,LuanClosure closure,String op,Object... args) throws LuanException;
+	}
+
+	private static String SECURITY_KEY = "Luan.Security";
+
+	public static void checkSecurity(LuanState luan,String op,Object... args) throws LuanException {
+		check(luan,1,op,args);
+	}
+
+	public static void checkCallerSecurity(LuanState luan,String op,Object... args) throws LuanException {
+		check(luan,2,op,args);
+	}
+
+	private static void check(LuanState luan,int i,String op,Object... args) throws LuanException {
+		Security s = (Security)luan.registry().get(SECURITY_KEY);
+		if( s!=null )
+			s.check(luan,luan.peek(),op,args);
+	}
+
+	public static Security setSecurity(LuanState luan,Security s) {
+		return (Security)luan.registry().put(SECURITY_KEY,s);
+	}
+
+
 	private Luan() {}  // never
 }
diff -r 5a39b006acd1 -r f41919741100 src/luan/LuanClosure.java
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/luan/LuanClosure.java	Mon Feb 11 01:38:55 2019 -0700
@@ -0,0 +1,62 @@
+package luan;
+
+import luan.impl.Pointer;
+
+
+public abstract class LuanClosure extends LuanFunction implements LuanCloneable, Cloneable {
+	public Pointer[] upValues;
+	public boolean javaOk;
+	public final String sourceName;
+	private LuanCloner cloner;
+
+	public LuanClosure(int nUpValues,boolean javaOk,String sourceName) throws LuanException {
+		this.upValues = new Pointer[nUpValues];
+		this.javaOk = javaOk;
+		this.sourceName = sourceName;
+	}
+
+	@Override public LuanClosure shallowClone() {
+		check();
+		try {
+			return (LuanClosure)clone();
+		} catch(CloneNotSupportedException e) {
+			throw new RuntimeException(e);
+		}
+	}
+
+	@Override public void deepenClone(LuanCloneable dc,LuanCloner cloner) {
+		LuanClosure clone = (LuanClosure)dc;
+		switch( cloner.type ) {
+		case COMPLETE:
+			clone.upValues = (Pointer[])cloner.clone(upValues);
+			return;
+		case INCREMENTAL:
+			clone.cloner = cloner;
+			clone.upValues = upValues;
+			return;
+		}
+	}
+
+	private void check() {
+		if( cloner != null ) {
+			upValues = (Pointer[])cloner.clone(upValues);
+			cloner = null;
+		}
+	}
+
+	@Override public final Object call(LuanState luan,Object[] args) throws LuanException {
+		if( luan.isLocked )
+			throw new RuntimeException("luan is locked");
+		check();
+		luan.push(this);
+		try {
+			return doCall(luan,args);
+		} catch(StackOverflowError e) {
+			throw new LuanException( "stack overflow" );
+		} finally {
+			luan.pop();
+		}	
+	}
+
+	public abstract Object doCall(LuanState luan,Object[] args) throws LuanException;
+}
diff -r 5a39b006acd1 -r f41919741100 src/luan/LuanException.java
--- a/src/luan/LuanException.java	Sun Feb 10 02:01:49 2019 -0700
+++ b/src/luan/LuanException.java	Mon Feb 11 01:38:55 2019 -0700
@@ -95,13 +95,13 @@
 			sb.append( "\nCaused by: " ).append( getJavaStackTraceString(cause) );
 		return sb.toString();
 	}
-
+/*
 	public static String currentSource() {
 		LuanException ex = new LuanException("currentSource");
 		List<StackTraceElement> st = ex.justLuan(ex.getStackTrace());
 		return st.isEmpty() ? null : st.get(0).getFileName();
 	}
-
+*/
 	@Override public void printStackTrace(PrintStream s) {
 		s.print("Luan: ");
 		s.println(luanStackTrace());
diff -r 5a39b006acd1 -r f41919741100 src/luan/LuanJavaOk.java
--- a/src/luan/LuanJavaOk.java	Sun Feb 10 02:01:49 2019 -0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,16 +0,0 @@
-package luan;
-
-
-public final class LuanJavaOk implements LuanCloneable {
-	public boolean ok = false;
-	public boolean unrestrictedIo = false;
-
-	@Override public LuanJavaOk shallowClone() {
-		LuanJavaOk javaOk = new LuanJavaOk();
-		javaOk.ok = ok;
-		javaOk.unrestrictedIo = unrestrictedIo;
-		return javaOk;
-	}
-
-	@Override public void deepenClone(LuanCloneable clone,LuanCloner cloner) {}
-}
diff -r 5a39b006acd1 -r f41919741100 src/luan/LuanState.java
--- a/src/luan/LuanState.java	Sun Feb 10 02:01:49 2019 -0700
+++ b/src/luan/LuanState.java	Mon Feb 11 01:38:55 2019 -0700
@@ -20,7 +20,7 @@
 public final class LuanState implements LuanCloneable {
 	private static final Logger logger = LoggerFactory.getLogger(LuanState.class);
 
-	public LuanJavaOk javaOk;
+	private final List<LuanClosure> stack = new ArrayList<LuanClosure>();
 	private Map registry;
 	public boolean isLocked = false;
 
@@ -30,7 +30,6 @@
 	public OnClose onClose;
 
 	public LuanState() {
-		javaOk = new LuanJavaOk();
 		registry = new HashMap();
 	}
 
@@ -43,11 +42,27 @@
 	@Override public void deepenClone(LuanCloneable dc,LuanCloner cloner) {
 		LuanState clone = (LuanState)dc;
 		clone.registry = cloner.clone(registry);
-		clone.javaOk = (LuanJavaOk)cloner.clone(javaOk);
 		if( cloner.type == LuanCloner.Type.INCREMENTAL )
 			isLocked = true;
 	}
 
+	public LuanClosure peek() {
+		return peek(1);
+	}
+
+	public LuanClosure peek(int i) {
+		int n = stack.size();
+		return n < i ? null : stack.get(n-i);
+	}
+
+	void push(LuanClosure closure) {
+		stack.add(closure);
+	}
+
+	void pop() {
+		stack.remove(stack.size()-1);
+	}
+
 	public Map registry() {
 		return registry;
 	}
@@ -84,7 +99,7 @@
 			LuanTable tbl = (LuanTable)obj;
 			return tbl.get(key);
 		}
-		if( obj != null && javaOk.ok )
+		if( obj != null && peek().javaOk )
 			return JavaLuan.__index(this,obj,key);
 		throw new LuanException("attempt to index a " + Luan.type(obj) + " value" );
 	}
diff -r 5a39b006acd1 -r f41919741100 src/luan/LuanTable.java
--- a/src/luan/LuanTable.java	Sun Feb 10 02:01:49 2019 -0700
+++ b/src/luan/LuanTable.java	Mon Feb 11 01:38:55 2019 -0700
@@ -18,19 +18,20 @@
 	private Map map = null;
 	private List list = null;
 	private LuanTable metatable = null;
-	public LuanJavaOk javaOk;
+	public LuanClosure closure;
 	private LuanCloner cloner;
+	private String security = null;
 
 	public LuanTable(LuanState luan) {
 		this.luan = luan;
 	}
 
-	public LuanTable(LuanState luan,List list) {
+	public LuanTable(LuanState luan,List list){
 		int n = list.size();
 		for( int i=0; i<n; i++ ) {
 			Object val = list.get(i);
 			if( val != null )
-				rawPut(i+1,val);
+				rawPut2(i+1,val);
 		}
 	}
 
@@ -41,15 +42,15 @@
 			Object key = entry.getKey();
 			Object value = entry.getValue();
 			if( key != null && value != null )
-				rawPut(key,value);
+				rawPut2(key,value);
 		}
 	}
 
-	public LuanTable(LuanState luan,Set set) {
+	public LuanTable(LuanState luan,Set set){
 		this.luan = luan;
 		for( Object el : set ) {
 			if( el != null )
-				rawPut(el,Boolean.TRUE);
+				rawPut2(el,Boolean.TRUE);
 		}
 	}
 
@@ -69,6 +70,7 @@
 	@Override public void deepenClone(LuanCloneable dc,LuanCloner cloner) {
 		check();
 		LuanTable clone = (LuanTable)dc;
+		clone.security = security;
 		switch( cloner.type ) {
 		case COMPLETE:
 			deepenClone(clone,cloner);
@@ -78,7 +80,7 @@
 			clone.map = map;
 			clone.list = list;
 			clone.metatable = metatable;
-			clone.javaOk = javaOk;
+			clone.closure = closure;
 			return;
 		}
 	}
@@ -108,7 +110,7 @@
 			clone.list = newList;
 		}
 		clone.metatable = (LuanTable)cloner.clone(metatable);
-		clone.javaOk = (LuanJavaOk)cloner.clone(javaOk);
+		clone.closure = (LuanClosure)cloner.clone(closure);
 	}
 
 	public boolean isList() {
@@ -189,7 +191,13 @@
 		throw new LuanException("invalid type "+Luan.type(h)+" for metamethod __new_index");
 	}
 
-	public void rawPut(Object key,Object val) {
+	public void rawPut(Object key,Object val) throws LuanException {
+		if( security != null )
+			Luan.checkSecurity(luan,"table",security,"put",key,val);
+		rawPut2(key,val);
+	}
+
+	private void rawPut2(Object key,Object val) {
 		check();
 		Integer iT = Luan.asInteger(key);
 		if( iT != null ) {
@@ -437,7 +445,9 @@
 		return metatable;
 	}
 
-	public void setMetatable(LuanTable metatable) {
+	public void setMetatable(LuanTable metatable) throws LuanException {
+		if( security != null )
+			Luan.checkSecurity(luan,"table",security,"set_metatable",metatable);
 		check();
 		this.metatable = metatable;
 	}
@@ -516,4 +526,8 @@
 		LuanFunction fn = (LuanFunction)get(fnName);
 		return fn.call(luan,args);
 	}
+
+	public static void setSecurity(LuanTable tbl,String security) {
+		tbl.security = security;
+	}
 }
diff -r 5a39b006acd1 -r f41919741100 src/luan/host/Backup.java
--- a/src/luan/host/Backup.java	Sun Feb 10 02:01:49 2019 -0700
+++ b/src/luan/host/Backup.java	Mon Feb 11 01:38:55 2019 -0700
@@ -127,7 +127,7 @@
 
 	public static void main(String[] args) throws Exception {
 		Log4j.initForConsole();
-		WebHandler.allowJavaFileName = args[2];
+		WebHandler.securityPassword = args[2];
 		backup( new File(args[0]), new File(args[1]) );
 		System.exit(0);
 	}
diff -r 5a39b006acd1 -r f41919741100 src/luan/host/Init.luan
--- a/src/luan/host/Init.luan	Sun Feb 10 02:01:49 2019 -0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,76 +0,0 @@
-local Luan = require "luan:Luan.luan"
-local error = Luan.error
-local do_file = Luan.do_file or error()
-local String = require "luan:String.luan"
-local gsub = String.gsub or error()
-
-
-local Init = {}
-
-local dir, domain, logging = ...
-
-
--- logging
-
-if logging then
-	java()
-	local LuanLogger = require "java:luan.modules.logging.LuanLogger"
-	local Level = require "java:org.apache.log4j.Level"
-	local EnhancedPatternLayout = require "java:org.apache.log4j.EnhancedPatternLayout"
-	local RollingFileAppender = require "java:org.apache.log4j.RollingFileAppender"
-	
-	local logger = LuanLogger.getRootLogger()
-	logger.removeAllAppenders()
-	local layout = EnhancedPatternLayout.new("%d %-5p %c - %m%n")
-	local log_dir = dir.."/site/private/local/logs/"
-
-	local function add_appender(file,level)
-		local appender = RollingFileAppender.new(layout, log_dir..file)
-		appender.setMaxFileSize("1MB")
-		logger.addAppender(appender)
-		if level ~= logger.getEffectiveLevel() then
-			appender.setThreshold(level)
-		end
-	end
-
-	add_appender("error.log",Level.ERROR)
-	add_appender("warn.log",Level.WARN)
-	add_appender("info.log",Level.INFO)
-end
-
-
-
--- set vars
-
-local Io = require "luan:Io.luan"
-local Http = require "luan:http/Http.luan"
-local Hosting = require "luan:host/Hosting.luan"
-local Mail = require "luan:mail/Mail.luan"
-
-Init.password = do_file(dir.."/info.luan").password or error()
-
-Http.dir = "file:"..dir.."/site"
-
-function Io.schemes.site(path,loading)
-	return Io.uri( Http.dir..path, loading )
-end
-
-Http.domain = domain
-Io.password = Init.password
-
-
--- mail  - fix later
-
-Hosting.send_mail = Mail.Sender{
-	host = "smtpcorp.com"
-	username = "smtp@luan.ws"  -- ?
-	password = "luanhost"
-	port = 2525
-}.send
-
-
--- callback to luanhost code
-do_file "file:init.luan"
-
-
-return Init
diff -r 5a39b006acd1 -r f41919741100 src/luan/host/WebHandler.java
--- a/src/luan/host/WebHandler.java	Sun Feb 10 02:01:49 2019 -0700
+++ b/src/luan/host/WebHandler.java	Mon Feb 11 01:38:55 2019 -0700
@@ -7,11 +7,13 @@
 import luan.webserver.Request;
 import luan.webserver.Response;
 import luan.webserver.handlers.DomainHandler;
+import luan.Luan;
 import luan.LuanState;
 import luan.LuanException;
 import luan.LuanTable;
-import luan.modules.IoLuan;
-import luan.modules.JavaLuan;
+import luan.LuanFunction;
+import luan.LuanClosure;
+import luan.modules.BasicLuan;
 import luan.modules.http.LuanHandler;
 import luan.modules.logging.LuanLogger;
 
@@ -19,6 +21,14 @@
 public class WebHandler implements Handler {
 	private static final Logger logger = LoggerFactory.getLogger(WebHandler.class);
 
+	private static final class LuanRuntimeException extends RuntimeException {
+		final LuanException e;
+
+		LuanRuntimeException(LuanException e) {
+			this.e = e;
+		}
+	}
+
 	private static final DomainHandler.Factory factory = new DomainHandler.Factory() {
 		public Handler newHandler(String domain) {
 			File dir = new File(sitesDir,domain);
@@ -40,7 +50,7 @@
 		}
 	};
 
-	public static String allowJavaFileName = "allow_java";  // change for security
+	public static String securityPassword = "password";  // change for security
 	private static final DomainHandler domainHandler = new DomainHandler(factory);
 	private static String sitesDir = null;
 
@@ -90,59 +100,62 @@
 		return true;
 	}
 */
-	static LuanTable initLuan(LuanState luan,String dir,String domain,boolean logging) {
-		LuanTable init;
+	static void initLuan(LuanState luan,String dir,String domain,boolean logging) {
+		security(luan,dir);
 		try {
-			init = (LuanTable)luan.eval(
-				"local Luan = require 'luan:Luan.luan'\n"
-				+"local f = Luan.load_file 'classpath:luan/host/Init.luan'\n"
-				+"return f('"+dir+"','"+domain+"',"+logging+")\n"
-			);
+			LuanFunction fn = BasicLuan.load_file(luan,"classpath:luan/host/init.luan");
+			fn.call(luan,new Object[]{dir,domain,logging});
 		} catch(LuanException e) {
-			throw new RuntimeException(e);
+			throw new LuanRuntimeException(e);
 		}
-		File allowJavaFile = new File(dir,"site/private/"+allowJavaFileName);
-		if( !allowJavaFile.exists() ) {
-			JavaLuan.setSecurity( luan, javaSecurity );
-			IoLuan.setSecurity( luan, ioSecurity(dir) );
-		}
-		return init;
 	}
 
 	public static void removeHandler(String domain) {
 		domainHandler.removeHandler(domain);
 	}
 
-	public static void loadHandler(String domain) {
-		domainHandler.getHandler(domain);
+	public static void loadHandler(String domain) throws LuanException {
+		try {
+			domainHandler.getHandler(domain);
+		} catch(LuanRuntimeException e) {
+			throw e.e;
+		}
 	}
 
-	private static final IoLuan.Security ioSecurity(String dir) {
+	private static final void security(LuanState luan,String dir) {
 		final String siteUri = "file:" + dir + "/site";
-		return new IoLuan.Security() {
-			public void check(LuanState luan,String name) throws LuanException {
-				if( name.startsWith("file:") ) {
-					if( name.contains("..") )
-						throw new LuanException("Security violation - '"+name+"' contains '..'");
-					if( !(name.equals(siteUri) || name.startsWith(siteUri+"/")) )
-						throw new LuanException("Security violation - '"+name+"' outside of site dir");
-				}
-				else if( name.startsWith("classpath:luan/host/") ) {
-					throw new LuanException("Security violation");
-				}
-				else if( name.startsWith("os:") || name.startsWith("bash:") ) {
-					throw new LuanException("Security violation");
+		Luan.Security security = new Luan.Security() {
+			public void check(LuanState luan,LuanClosure closure,String op,Object... args)
+				throws LuanException
+			{
+				if( op.equals("uri") ) {
+					String name = (String)args[0];
+					if( name.startsWith("file:") ) {
+						if( name.contains("..") )
+							throw new LuanException("Security violation - '"+name+"' contains '..'");
+						if( !(name.equals(siteUri) || name.startsWith(siteUri+"/")) )
+							throw new LuanException("Security violation - '"+name+"' outside of site dir");
+					}
+					else if( name.startsWith("classpath:luan/host/") ) {
+						throw new LuanException("Security violation");
+					}
+					else if( name.startsWith("os:") || name.startsWith("bash:") ) {
+						throw new LuanException("Security violation");
+					}
+				} else {
+					String name = closure.sourceName;
+					if( !(
+						name.startsWith("luan:")
+						|| name.startsWith("classpath:")
+						|| name.matches("^file:[^/]+$")
+					) )
+						throw new LuanException("Security violation - only luan:* modules can load Java");
+					if( name.equals("luan:logging/Logging") )
+						throw new LuanException("Security violation - cannot reload Logging");
 				}
 			}
 		};
+		Luan.setSecurity(luan,security);
 	}
 
-	private static final JavaLuan.Security javaSecurity = new JavaLuan.Security() {
-		public void check(LuanState luan,String name) throws LuanException {
-			if( !(name.startsWith("luan:") || name.matches("^file:[^/]+$")) )
-				throw new LuanException("Security violation - only luan:* modules can load Java");
-			if( name.equals("luan:logging/Logging") )
-				throw new LuanException("Security violation - cannot reload Logging");
-		}
-	};
 }
diff -r 5a39b006acd1 -r f41919741100 src/luan/host/init.luan
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/luan/host/init.luan	Mon Feb 11 01:38:55 2019 -0700
@@ -0,0 +1,81 @@
+local Luan = require "luan:Luan.luan"
+local error = Luan.error
+local do_file = Luan.do_file or error()
+local String = require "luan:String.luan"
+local gsub = String.gsub or error()
+
+
+local dir, domain, logging = ...
+
+
+-- logging
+
+if logging then
+	java()
+	local LuanLogger = require "java:luan.modules.logging.LuanLogger"
+	local Level = require "java:org.apache.log4j.Level"
+	local EnhancedPatternLayout = require "java:org.apache.log4j.EnhancedPatternLayout"
+	local RollingFileAppender = require "java:org.apache.log4j.RollingFileAppender"
+	
+	local logger = LuanLogger.getRootLogger()
+	logger.removeAllAppenders()
+	local layout = EnhancedPatternLayout.new("%d %-5p %c - %m%n")
+	local log_dir = dir.."/site/private/local/logs/"
+
+	local function add_appender(file,level)
+		local appender = RollingFileAppender.new(layout, log_dir..file)
+		appender.setMaxFileSize("1MB")
+		logger.addAppender(appender)
+		if level ~= logger.getEffectiveLevel() then
+			appender.setThreshold(level)
+		end
+	end
+
+	add_appender("error.log",Level.ERROR)
+	add_appender("warn.log",Level.WARN)
+	add_appender("info.log",Level.INFO)
+end
+
+
+
+-- set vars
+
+local Io = require "luan:Io.luan"
+local Http = require "luan:http/Http.luan"
+local Hosting = require "luan:host/Hosting.luan"
+local Mail = require "luan:mail/Mail.luan"
+
+Io.password = do_file(dir.."/info.luan").password or error()
+
+Http.dir = "file:"..dir.."/site"
+
+function Io.schemes.site(path,loading)
+	return Io.uri( Http.dir..path, loading )
+end
+
+Http.domain = domain
+
+
+-- mail  - fix later
+
+Hosting.send_mail = Mail.Sender{
+	host = "smtpcorp.com"
+	username = "smtp@luan.ws"  -- ?
+	password = "luanhost"
+	port = 2525
+}.send
+
+
+-- callback to luanhost code
+do_file "file:init.luan"
+
+
+
+java()
+local WebHandler = require "java:luan.host.WebHandler"
+local LuanJava = require "java:luan.Luan"
+
+function Hosting.no_security(password)
+	WebHandler.securityPassword == password or error "wrong password"
+	LuanJava.setSecurity(nil)
+end
diff -r 5a39b006acd1 -r f41919741100 src/luan/impl/Closure.java
--- a/src/luan/impl/Closure.java	Sun Feb 10 02:01:49 2019 -0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,70 +0,0 @@
-package luan.impl;
-
-import luan.Luan;
-import luan.LuanFunction;
-import luan.LuanState;
-import luan.LuanException;
-import luan.LuanCloner;
-import luan.LuanCloneable;
-import luan.LuanJavaOk;
-
-
-public abstract class Closure extends LuanFunction implements LuanCloneable, Cloneable {
-	protected Pointer[] upValues;
-	protected LuanJavaOk javaOk;
-	private LuanCloner cloner;
-
-	public Closure(int nUpValues,LuanJavaOk javaOk) throws LuanException {
-		this.upValues = new Pointer[nUpValues];
-		this.javaOk = javaOk;
-	}
-
-	@Override public Closure shallowClone() {
-		check();
-		try {
-			return (Closure)clone();
-		} catch(CloneNotSupportedException e) {
-			throw new RuntimeException(e);
-		}
-	}
-
-	@Override public void deepenClone(LuanCloneable dc,LuanCloner cloner) {
-		Closure clone = (Closure)dc;
-		switch( cloner.type ) {
-		case COMPLETE:
-			clone.upValues = (Pointer[])cloner.clone(upValues);
-			clone.javaOk = (LuanJavaOk)cloner.clone(javaOk);
-			return;
-		case INCREMENTAL:
-			clone.cloner = cloner;
-			clone.upValues = upValues;
-			clone.javaOk = javaOk;
-			return;
-		}
-	}
-
-	private void check() {
-		if( cloner != null ) {
-			upValues = (Pointer[])cloner.clone(upValues);
-			javaOk = (LuanJavaOk)cloner.clone(javaOk);
-			cloner = null;
-		}
-	}
-
-	@Override public final Object call(LuanState luan,Object[] args) throws LuanException {
-		if( luan.isLocked )
-			throw new RuntimeException("luan is locked");
-		check();
-		LuanJavaOk old = luan.javaOk;
-		luan.javaOk = javaOk;
-		try {
-			return doCall(luan,args);
-		} catch(StackOverflowError e) {
-			throw new LuanException( "stack overflow" );
-		} finally {
-			luan.javaOk = old;
-		}	
-	}
-
-	public abstract Object doCall(LuanState luan,Object[] args) throws LuanException;
-}
diff -r 5a39b006acd1 -r f41919741100 src/luan/impl/LuanCompiler.java
--- a/src/luan/impl/LuanCompiler.java	Sun Feb 10 02:01:49 2019 -0700
+++ b/src/luan/impl/LuanCompiler.java	Mon Feb 11 01:38:55 2019 -0700
@@ -8,7 +8,7 @@
 import luan.LuanState;
 import luan.LuanException;
 import luan.LuanTable;
-import luan.LuanJavaOk;
+import luan.LuanClosure;
 import luan.modules.JavaLuan;
 import luan.modules.PackageLuan;
 
@@ -18,19 +18,12 @@
 
 	public static LuanFunction compile(String sourceText,String sourceName,LuanTable env) throws LuanException {
 		Class fnClass = env==null ? getClass(sourceText,sourceName) : getClass(sourceText,sourceName,env);
-		LuanJavaOk javaOk;
-		if( env == null ) {
-			javaOk = new LuanJavaOk();
-		} else {
-			javaOk = env.javaOk;
-			if( javaOk == null ) {
-				javaOk = new LuanJavaOk();
-				env.javaOk = javaOk;
-			}
-		}
-		Closure closure;
+		boolean javaOk = false;
+		if( env != null && env.closure != null )
+			javaOk = env.closure.javaOk;
+		LuanClosure closure;
 		try {
-			closure = (Closure)fnClass.getConstructor(LuanJavaOk.class).newInstance(javaOk);
+			closure = (LuanClosure)fnClass.getConstructor(Boolean.TYPE,String.class).newInstance(javaOk,sourceName);
 		} catch(NoSuchMethodException e) {
 			throw new RuntimeException(e);
 		} catch(InstantiationException e) {
@@ -42,7 +35,10 @@
 		}
 		closure.upValues[0].o = JavaLuan.javaFn;
 		closure.upValues[1].o = PackageLuan.requireFn;
-		if( env != null )  closure.upValues[2].o = env;
+		if( env != null ) {
+			closure.upValues[2].o = env;
+			env.closure = closure;
+		}
 		return closure;
 	}
 
diff -r 5a39b006acd1 -r f41919741100 src/luan/impl/LuanImpl.java
--- a/src/luan/impl/LuanImpl.java	Sun Feb 10 02:01:49 2019 -0700
+++ b/src/luan/impl/LuanImpl.java	Mon Feb 11 01:38:55 2019 -0700
@@ -162,7 +162,7 @@
 			tbl.put(key,value);
 			return;
 		}
-		if( t != null && luan.javaOk.ok )
+		if( t != null && luan.peek().javaOk )
 			JavaLuan.__new_index(luan,t,key,value);
 		else
 			throw new LuanException( "attempt to index a " + Luan.type(t) + " value" );
@@ -229,7 +229,7 @@
 		}
 	}
 
-	public static LuanTable table(LuanState luan,Object[] a) {
+	public static LuanTable table(LuanState luan,Object[] a) throws LuanException {
 		LuanTable table = new LuanTable(luan);
 		int i = 0;
 		for( Object fld : a ) {
diff -r 5a39b006acd1 -r f41919741100 src/luan/impl/LuanParser.java
--- a/src/luan/impl/LuanParser.java	Sun Feb 10 02:01:49 2019 -0700
+++ b/src/luan/impl/LuanParser.java	Mon Feb 11 01:38:55 2019 -0700
@@ -2009,16 +2009,16 @@
 			stmts.add( "\nreturn LuanFunction.NOTHING;" );
 		return ""
 			+"package luan.impl;  "
+			+"import luan.LuanClosure;  "
 			+"import luan.Luan;  "
 			+"import luan.LuanFunction;  "
 			+"import luan.LuanState;  "
-			+"import luan.LuanJavaOk;  "
 			+"import luan.LuanException;  "
 			+"import luan.modules.PackageLuan;  "
 
-			+"public class " + className +" extends Closure {  "
-				+"public "+className+"(LuanJavaOk java) throws LuanException {  "
-					+"super("+upValueSymbols.size()+",java);  "
+			+"public class " + className +" extends LuanClosure {  "
+				+"public "+className+"(boolean javaOk,String sourceName) throws LuanException {  "
+					+"super("+upValueSymbols.size()+",javaOk,sourceName);  "
 					+ init(upValueSymbols)
 				+"}  "
 
@@ -2038,7 +2038,7 @@
 			stmt.add( "return LuanFunction.NOTHING;  " );
 		Expr exp = new Expr(Val.SINGLE,false);
 		exp.add( ""
-			+"new Closure("+upValueSymbols.size()+",javaOk) {  "
+			+"new LuanClosure("+upValueSymbols.size()+",javaOk,sourceName) {  "
 				+"{  "
 				+ init(upValueSymbols)
 				+"}  "
diff -r 5a39b006acd1 -r f41919741100 src/luan/modules/BasicLuan.java
--- a/src/luan/modules/BasicLuan.java	Sun Feb 10 02:01:49 2019 -0700
+++ b/src/luan/modules/BasicLuan.java	Mon Feb 11 01:38:55 2019 -0700
@@ -88,7 +88,7 @@
 		return table.rawGet(index);
 	}
 
-	public static void raw_set(LuanTable table,Object index,Object value) {
+	public static void raw_set(LuanTable table,Object index,Object value) throws LuanException {
 		table.rawPut(index,value);
 	}
 
@@ -240,4 +240,5 @@
 		return LuanToString.toString(obj,b);
 	}
 
+	private void BasicLuan() {}  // never
 }
diff -r 5a39b006acd1 -r f41919741100 src/luan/modules/Boot.luan
--- a/src/luan/modules/Boot.luan	Sun Feb 10 02:01:49 2019 -0700
+++ b/src/luan/modules/Boot.luan	Mon Feb 11 01:38:55 2019 -0700
@@ -5,11 +5,13 @@
 local new_error = BasicLuan.new_error
 local ipairs = BasicLuan.ipairs
 local set_metatable = BasicLuan.set_metatable
+local try = BasicLuan.try_
 local StringLuan = require "java:luan.modules.StringLuan"
 local match = StringLuan.match  -- String.match
 local IoLuan = require "java:luan.modules.IoLuan"
 local LuanUrl = require "java:luan.modules.url.LuanUrl"
-IoLuan.unrestricted()  -- not right
+local LuanJava = require "java:luan.Luan"
+local LuanTable = require "java:luan.LuanTable"
 
 
 local Boot = {}
@@ -20,6 +22,20 @@
 end
 Boot.error = error
 
+local function no_security(fn)
+	LuanJava.checkCallerSecurity("no_security")
+	return function(...)
+		local security = LuanJava.setSecurity(nil)
+		return try( {
+			fn
+			finally = function()
+				security and LuanJava.setSecurity(security)
+			end
+		}, ... )
+	end
+end
+Boot.no_security = no_security
+
 
 local function new_LuanIn(io)
 	local this = {}
@@ -69,6 +85,7 @@
 end
 
 local schemes = {}
+LuanTable.setSecurity(schemes,"schemes")
 
 function schemes.null(path)
 	return new_LuanIO( IoLuan.nullIO )
diff -r 5a39b006acd1 -r f41919741100 src/luan/modules/Io.luan
--- a/src/luan/modules/Io.luan	Sun Feb 10 02:01:49 2019 -0700
+++ b/src/luan/modules/Io.luan	Mon Feb 11 01:38:55 2019 -0700
@@ -14,7 +14,6 @@
 Io.stdin = Boot.new_LuanIn( IoLuan.defaultStdin )
 Io.stdout = Boot.text_writer(System.out)
 Io.stderr = Boot.text_writer(System.err)
-Io.unrestricted = IoLuan.unrestricted
 
 -- used by http and rpc
 Io.password = "password"
diff -r 5a39b006acd1 -r f41919741100 src/luan/modules/IoLuan.java
--- a/src/luan/modules/IoLuan.java	Sun Feb 10 02:01:49 2019 -0700
+++ b/src/luan/modules/IoLuan.java	Mon Feb 11 01:38:55 2019 -0700
@@ -28,6 +28,7 @@
 import java.nio.file.Files;
 import java.util.Enumeration;
 import java.util.Map;
+import luan.Luan;
 import luan.LuanState;
 import luan.LuanTable;
 import luan.LuanFunction;
@@ -640,44 +641,24 @@
 		}
 	}
 
-	public static LuanTable my_ips(LuanState luan) throws IOException {
+	public static LuanTable my_ips(LuanState luan) throws IOException, LuanException {
 		LuanTable tbl = new LuanTable(luan);
 		for( Enumeration<NetworkInterface> e1 = NetworkInterface.getNetworkInterfaces(); e1.hasMoreElements(); ) {
 			NetworkInterface ni = e1.nextElement();
 			for( Enumeration<InetAddress> e2 = ni.getInetAddresses(); e2.hasMoreElements(); ) {
 				InetAddress ia = e2.nextElement();
 				if( ia instanceof Inet4Address )
-					tbl.rawPut(ia.getHostAddress(),true);
+					tbl.put(ia.getHostAddress(),true);
 			}
 		}
 		return tbl;
 	}
 
 
-	// security
-
-	public static void unrestricted(LuanState luan) throws LuanException {
-		JavaLuan.check(luan);
-		luan.javaOk.unrestrictedIo = true;
-	}
-
-	public interface Security {
-		public void check(LuanState luan,String name) throws LuanException;
+	private static void check(LuanState luan,String name) throws LuanException {
+		Luan.checkSecurity(luan,"uri",name);
 	}
 
-	private static String SECURITY_KEY = "Io.Security";
-
-	private static void check(LuanState luan,String name) throws LuanException {
-		if( luan.javaOk.unrestrictedIo )
-			return;
-		Security s = (Security)luan.registry().get(SECURITY_KEY);
-		if( s!=null )
-			s.check(luan,name);
-	}
-
-	public static void setSecurity(LuanState luan,Security s) {
-		luan.registry().put(SECURITY_KEY,s);
-	}
 
 	private void IoLuan() {}  // never
 }
diff -r 5a39b006acd1 -r f41919741100 src/luan/modules/JavaLuan.java
--- a/src/luan/modules/JavaLuan.java	Sun Feb 10 02:01:49 2019 -0700
+++ b/src/luan/modules/JavaLuan.java	Mon Feb 11 01:38:55 2019 -0700
@@ -28,8 +28,8 @@
 public final class JavaLuan {
 
 	public static void java(LuanState luan) throws LuanException {
-		check(luan);
-		luan.javaOk.ok = true;
+		Luan.checkSecurity(luan,"java");
+		luan.peek().javaOk = true;
 	}
 
 	public static final LuanFunction javaFn;
@@ -42,7 +42,7 @@
 	}
 
 	private static void checkJava(LuanState luan) throws LuanException {
-		if( !luan.javaOk.ok )
+		if( !luan.peek().javaOk )
 			throw new LuanException("Java isn't allowed");
 	}
 
@@ -472,26 +472,5 @@
 	}
 */
 
-
-
-	// security
-
-	public interface Security {
-		public void check(LuanState luan,String name) throws LuanException;
-	}
-
-	private static String SECURITY_KEY = "Java.Security";
-
-	static void check(LuanState luan) throws LuanException {
-		Security s = (Security)luan.registry().get(SECURITY_KEY);
-		if( s!=null ) {
-			String name = LuanException.currentSource();
-			s.check(luan,name);
-		}
-	}
-
-	public static void setSecurity(LuanState luan,Security s) {
-		luan.registry().put(SECURITY_KEY,s);
-	}
-
+	private void JavaLuan() {}  // never
 }
diff -r 5a39b006acd1 -r f41919741100 src/luan/modules/PackageLuan.java
--- a/src/luan/modules/PackageLuan.java	Sun Feb 10 02:01:49 2019 -0700
+++ b/src/luan/modules/PackageLuan.java	Mon Feb 11 01:38:55 2019 -0700
@@ -93,10 +93,14 @@
 		} catch(LuanException e) {
 			throw new RuntimeException(e);
 		}
+		Luan.Security security = Luan.setSecurity(luan,null);
 		try {
 			return (String)Luan.first(boot.call("read",uri));
 		} catch(LuanException e) {
 			return null;
+		} finally {
+			if( security != null )
+				Luan.setSecurity(luan,security);
 		}
 	}
 
diff -r 5a39b006acd1 -r f41919741100 src/luan/modules/TableLuan.java
--- a/src/luan/modules/TableLuan.java	Sun Feb 10 02:01:49 2019 -0700
+++ b/src/luan/modules/TableLuan.java	Mon Feb 11 01:38:55 2019 -0700
@@ -84,7 +84,7 @@
 		}
 	}
 
-	public static LuanTable pack(LuanState luan,Object... args) {
+	public static LuanTable pack(LuanState luan,Object... args) throws LuanException {
 		LuanTable tbl = new LuanTable(luan,Arrays.asList(args));
 		tbl.rawPut( "n", args.length );
 		return tbl;
diff -r 5a39b006acd1 -r f41919741100 src/luan/modules/ThreadLuan.java
--- a/src/luan/modules/ThreadLuan.java	Sun Feb 10 02:01:49 2019 -0700
+++ b/src/luan/modules/ThreadLuan.java	Mon Feb 11 01:38:55 2019 -0700
@@ -130,7 +130,7 @@
 		}
 	}
 
-	private static Object makeSafe(LuanState luan,Object v) {
+	private static Object makeSafe(LuanState luan,Object v) throws LuanException {
 		if( v instanceof LuanTable ) {
 			LuanTable tbl = (LuanTable)v;
 			if( tbl.getMetatable() != null )
diff -r 5a39b006acd1 -r f41919741100 src/luan/modules/lucene/Lucene.luan
--- a/src/luan/modules/lucene/Lucene.luan	Sun Feb 10 02:01:49 2019 -0700
+++ b/src/luan/modules/lucene/Lucene.luan	Mon Feb 11 01:38:55 2019 -0700
@@ -6,7 +6,6 @@
 local set_metatable = Luan.set_metatable or error()
 local Html = require "luan:Html.luan"
 local Io = require "luan:Io.luan"
-Io.unrestricted()
 local uri = Io.uri or error()
 local String = require "luan:String.luan"
 local matches = String.matches or error()
diff -r 5a39b006acd1 -r f41919741100 src/luan/modules/parsers/Css.java
--- a/src/luan/modules/parsers/Css.java	Sun Feb 10 02:01:49 2019 -0700
+++ b/src/luan/modules/parsers/Css.java	Mon Feb 11 01:38:55 2019 -0700
@@ -2,13 +2,18 @@
 
 import luan.LuanState;
 import luan.LuanTable;
+import luan.LuanException;
 import luan.lib.parser.Parser;
 
 
 public final class Css {
 
 	public static LuanTable style(LuanState luan,String text) {
-		return new Css(luan,text).parseStyle();
+		try {
+			return new Css(luan,text).parseStyle();
+		} catch(LuanException e) {
+			throw new RuntimeException(e);
+		}
 	}
 
 	private final LuanState luan;
@@ -19,7 +24,7 @@
 		this.parser = new Parser(text);
 	}
 
-	private LuanTable parseStyle() {
+	private LuanTable parseStyle() throws LuanException {
 		LuanTable tbl = new LuanTable(luan);
 		while( matchSpace() );
 		while( !parser.endOfInput() ) {
@@ -37,7 +42,7 @@
 			while( !parser.endOfInput() && parser.noneOf(";") );
 			String val = parser.textFrom(start).trim();
 
-			tbl.rawPut(prop,val);
+			tbl.put(prop,val);
 			parser.match(';');
 			while( matchSpace() );
 		}
diff -r 5a39b006acd1 -r f41919741100 src/luan/modules/parsers/Csv.java
--- a/src/luan/modules/parsers/Csv.java	Sun Feb 10 02:01:49 2019 -0700
+++ b/src/luan/modules/parsers/Csv.java	Mon Feb 11 01:38:55 2019 -0700
@@ -2,6 +2,7 @@
 
 import luan.LuanState;
 import luan.LuanTable;
+import luan.LuanException;
 import luan.lib.parser.Parser;
 import luan.lib.parser.ParseException;
 
@@ -9,7 +10,11 @@
 public final class Csv {
 
 	public static LuanTable toList(LuanState luan,String line) throws ParseException {
-		return new Csv(line).parse(luan);
+		try {
+			return new Csv(line).parse(luan);
+		} catch(LuanException e) {
+			throw new RuntimeException(e);
+		}
 	}
 
 	private final Parser parser;
@@ -22,12 +27,12 @@
 		return new ParseException(parser,msg);
 	}
 
-	private LuanTable parse(LuanState luan) throws ParseException {
+	private LuanTable parse(LuanState luan) throws ParseException, LuanException {
 		LuanTable list = new LuanTable(luan);
 		while(true) {
 			Spaces();
 			String field = parseField();
-			list.rawPut(list.rawLength()+1,field);
+			list.put(list.rawLength()+1,field);
 			Spaces();
 			if( parser.endOfInput() )
 				return list;
diff -r 5a39b006acd1 -r f41919741100 src/luan/modules/parsers/Html.java
--- a/src/luan/modules/parsers/Html.java	Sun Feb 10 02:01:49 2019 -0700
+++ b/src/luan/modules/parsers/Html.java	Mon Feb 11 01:38:55 2019 -0700
@@ -6,13 +6,18 @@
 import java.util.HashSet;
 import luan.LuanState;
 import luan.LuanTable;
+import luan.LuanException;
 import luan.lib.parser.Parser;
 
 
 public final class Html {
 
 	public static LuanTable toList(LuanState luan,String text,LuanTable containerTagsTbl) {
-		return new Html(luan,text,containerTagsTbl).parse();
+		try {
+			return new Html(luan,text,containerTagsTbl).parse();
+		} catch(LuanException e) {
+			throw new RuntimeException(e);
+		}
 	}
 
 	private final LuanState luan;
@@ -27,7 +32,7 @@
 		}
 	}
 
-	private LuanTable parse() {
+	private LuanTable parse() throws LuanException {
 		List list = new ArrayList();
 		StringBuilder sb = new StringBuilder();
 		while( !parser.endOfInput() ) {
@@ -61,7 +66,7 @@
 		return new LuanTable(luan,list);
 	}
 
-	private LuanTable parseComment() {
+	private LuanTable parseComment() throws LuanException {
 		parser.begin();
 		if( !parser.match("<!--") )
 			return parser.failure(null);
@@ -72,12 +77,12 @@
 		}
 		String text = parser.textFrom(start);
 		LuanTable tbl = new LuanTable(luan);
-		tbl.rawPut("type","comment");
-		tbl.rawPut("text",text);
+		tbl.put("type","comment");
+		tbl.put("text",text);
 		return parser.success(tbl);
 	}
 
-	private LuanTable parseCdata() {
+	private LuanTable parseCdata() throws LuanException {
 		parser.begin();
 		if( !parser.match("<![CDATA[") )
 			return parser.failure(null);
@@ -88,12 +93,12 @@
 		}
 		String text = parser.textFrom(start);
 		LuanTable tbl = new LuanTable(luan);
-		tbl.rawPut("type","cdata");
-		tbl.rawPut("text",text);
+		tbl.put("type","cdata");
+		tbl.put("text",text);
 		return parser.success(tbl);
 	}
 
-	private LuanTable parseContainer(LuanTable tag) {
+	private LuanTable parseContainer(LuanTable tag) throws LuanException {
 		String endTagName = '/' + (String)tag.rawGet("name");
 		int start = parser.begin();
 		int end;
@@ -110,15 +115,15 @@
 		}
 		String text = parser.text.substring(start,end);
 		LuanTable tbl = new LuanTable(luan);
-		tbl.rawPut("type","container");
-		tbl.rawPut("tag",tag);
-		tbl.rawPut("text",text);
+		tbl.put("type","container");
+		tbl.put("tag",tag);
+		tbl.put("text",text);
 		return parser.success(tbl);
 	}
 
-	private LuanTable parseTag() {
+	private LuanTable parseTag() throws LuanException {
 		LuanTable tbl = new LuanTable(luan);
-		tbl.rawPut("type","tag");
+		tbl.put("type","tag");
 		int tagStart = parser.begin();
 		if( !parser.match('<') )
 			return parser.failure(null);
@@ -128,26 +133,26 @@
 			return parser.failure(null);
 		while( matchNameChar() );
 		String name = parser.textFrom(start).toLowerCase();
-		tbl.rawPut("name",name);
+		tbl.put("name",name);
 		LuanTable attributes = new LuanTable(luan);
-		tbl.rawPut("attributes",attributes);
+		tbl.put("attributes",attributes);
 		String attrName;
 		while( (attrName = parseAttrName()) != null ) {
 			String attrValue = parseAttrValue();
-			attributes.rawPut( attrName, attrValue!=null ? attrValue : true );
+			attributes.put( attrName, attrValue!=null ? attrValue : true );
 			if( attrName.equals("style") && attrValue!=null ) {
 				LuanTable style = Css.style(luan,attrValue);
 				if( style!=null )
-					tbl.rawPut("style",style);
+					tbl.put("style",style);
 			}
 		}
 		while( matchSpace() );
 		boolean isEmpty = parser.match('/');
-		tbl.rawPut("is_empty",isEmpty);
+		tbl.put("is_empty",isEmpty);
 		if( !parser.match('>') )
 			return parser.failure(null);
 		String raw = parser.textFrom(tagStart);
-		tbl.rawPut("raw",raw);
+		tbl.put("raw",raw);
 		return parser.success(tbl);
 	}