/*
* Copyright (c) 2004-2015 Tada AB and other contributors, as listed below.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the The BSD 3-Clause License
* which accompanies this distribution, and is available at
* http://opensource.org/licenses/BSD-3-Clause
*
* Contributors:
* Tada AB
* Purdue University
*/
package org.postgresql.pljava.internal;
import java.io.File;
import java.io.FilePermission;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;
import java.security.Permission;
import java.sql.SQLException;
import java.sql.SQLDataException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.PropertyPermission;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.postgresql.pljava.management.Commands;
import org.postgresql.pljava.sqlgen.Lexicals.Identifier;
import static org.postgresql.pljava.sqlgen.Lexicals.identifierFrom;
import static
org.postgresql.pljava.sqlgen.Lexicals.ISO_AND_PG_IDENTIFIER_CAPTURING;
/**
* Provides access to some useful routines in the PostgreSQL server.
* @author Thomas Hallgren
*/
public class Backend
{
/**
* All native calls synchronize on this object.
*/
public static final Object THREADLOCK = new Object();
private static Session s_session;
private static final Pattern s_gucList = Pattern.compile(String.format(
"\\G(?:%1$s)(?<more>,\\s*+)?+", ISO_AND_PG_IDENTIFIER_CAPTURING));
public static synchronized Session getSession()
{
if(s_session == null)
s_session = new Session();
return s_session;
}
/**
* Returns the configuration option as read from the Global
* Unified Config package (GUC).
* @param key The name of the option.
* @return The value of the option.
*/
public static String getConfigOption(String key)
{
synchronized(THREADLOCK)
{
return _getConfigOption(key);
}
}
public static List<Identifier> getListConfigOption(String key)
throws SQLException
{
final Matcher m = s_gucList.matcher(getConfigOption(key));
ArrayList<Identifier> al = new ArrayList<>();
while ( m.find() )
{
al.add(identifierFrom(m));
if ( null != m.group("more") )
continue;
if ( ! m.hitEnd() )
throw new SQLDataException(String.format(
"configuration option \"%1$s\" improper list syntax",
key), "22P02");
}
al.trimToSize();
return Collections.unmodifiableList(al);
}
/**
* Returns the size of the statement cache.
* @return the size of the statement cache.
*/
public static int getStatementCacheSize()
{
synchronized(THREADLOCK)
{
return _getStatementCacheSize();
}
}
/**
* Log a message using the internal elog command.
* @param logLevel The log level as defined in
* {@link ELogHandler}.
* @param str The message
*/
static void log(int logLevel, String str)
{
synchronized(THREADLOCK)
{
_log(logLevel, str);
}
}
private static class PLJavaSecurityManager extends SecurityManager
{
private boolean m_recursion = false;
public void checkPermission(Permission perm)
{
this.nonRecursiveCheck(perm);
}
public void checkPermission(Permission perm, Object context)
{
this.nonRecursiveCheck(perm);
}
private synchronized void nonRecursiveCheck(Permission perm)
{
if(m_recursion)
//
// Something, probably a ClassLoader
// loading one of the referenced
// classes, caused a recursion. Well
// everything done within this method
// is permitted so we just return
// here.
//
return;
m_recursion = true;
try
{
this.assertPermission(perm);
}
finally
{
m_recursion = false;
}
}
void assertPermission(Permission perm)
{
if(perm instanceof RuntimePermission)
{
String name = perm.getName();
if("*".equals(name) || "exitVM".equals(name))
throw new SecurityException();
else if("setSecurityManager".equals(name)
&& !s_inSetTrusted)
//
// Attempt to set another
// security manager while not
// in the setTrusted method
//
throw new SecurityException();
}
else if(perm instanceof PropertyPermission)
{
if(perm.getActions().indexOf("write") >= 0)
{
// We never allow this to be changed.
// As for UDT byteorder, the classes that use it only check
// once so it would be misleading to allow runtime changes;
// use pljava.vmoptions to provide an initial value.
//
String propName = perm.getName();
if ( propName.equals("java.home") || propName.matches(
"org\\.postgresql\\.pljava\\.udt\\.byteorder(?:\\..*)?")
)
throw new SecurityException();
}
}
}
}
private static boolean s_inSetTrusted = false;
private static final SecurityManager s_untrustedSecurityManager = new PLJavaSecurityManager();
/**
* This security manager will block all attempts to access the file system
*/
private static final SecurityManager s_trustedSecurityManager = new PLJavaSecurityManager()
{
void assertPermission(Permission perm)
{
if(perm instanceof FilePermission)
{
String actions = perm.getActions();
if("read".equals(actions))
{
// Allow read of /dev/random
// and /dev/urandom
String fileName = perm.getName();
if ( "/dev/random".equals( fileName )
||
"/dev/urandom".equals( fileName )
)
return;
// Must be able to read
// timezone info etc. in the
// java installation
// directory.
//
File javaHome = new File(System.getProperty("java.home"));
File accessedFile = new File(perm.getName());
File fileDir = accessedFile.getParentFile();
while(fileDir != null)
{
if(fileDir.equals(javaHome))
return;
fileDir = fileDir.getParentFile();
}
}
throw new SecurityException(perm.getActions() + " on " + perm.getName());
}
super.assertPermission(perm);
}
};
public static void addClassImages(int jarId, String urlString)
throws SQLException
{
InputStream urlStream = null;
boolean wasTrusted = (System.getSecurityManager() == s_trustedSecurityManager);
if(wasTrusted)
setTrusted(false);
try
{
URL url = new URL(urlString);
URLConnection uc = url.openConnection();
uc.connect();
int sz = uc.getContentLength(); // once java6 obsolete, use ...Long
urlStream = uc.getInputStream();
Commands.addClassImages(jarId, urlStream, sz);
}
catch(IOException e)
{
throw new SQLException("I/O exception reading jar file: " + e.getMessage());
}
finally
{
if(urlStream != null)
try { urlStream.close(); } catch(IOException e) {}
if(wasTrusted)
setTrusted(true);
}
}
public static void clearFunctionCache()
{
synchronized(THREADLOCK)
{
_clearFunctionCache();
}
}
public static boolean isCreatingExtension()
{
synchronized(THREADLOCK)
{
return _isCreatingExtension();
}
}
/**
* Called when the JVM is first booted and then everytime a switch
* is made between calling a trusted function versus an untrusted
* function.
*/
private static void setTrusted(boolean trusted)
{
s_inSetTrusted = true;
try
{
Logger log = Logger.getAnonymousLogger();
if(log.isLoggable(Level.FINE))
log.fine("Using SecurityManager for " + (trusted ? "trusted" : "untrusted") + " language");
System.setSecurityManager(trusted ? s_trustedSecurityManager : s_untrustedSecurityManager);
}
finally
{
s_inSetTrusted = false;
}
}
/**
* Returns <code>true</code> if the backend is awaiting a return from a
* call into the JVM. This method will only return <code>false</code>
* when called from a thread other then the main thread and the main
* thread has returned from the call into the JVM.
*/
public native static boolean isCallingJava();
/**
* Returns the value of the GUC custom variable <code>
* pljava.release_lingering_savepoints</code>.
*/
public native static boolean isReleaseLingeringSavepoints();
private native static String _getConfigOption(String key);
private native static int _getStatementCacheSize();
private native static void _log(int logLevel, String str);
private native static void _clearFunctionCache();
private native static boolean _isCreatingExtension();
}