summaryrefslogtreecommitdiffstats
path: root/stoc/source/security
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:06:44 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:06:44 +0000
commited5640d8b587fbcfed7dd7967f3de04b37a76f26 (patch)
tree7a5f7c6c9d02226d7471cb3cc8fbbf631b415303 /stoc/source/security
parentInitial commit. (diff)
downloadlibreoffice-upstream.tar.xz
libreoffice-upstream.zip
Adding upstream version 4:7.4.7.upstream/4%7.4.7upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'stoc/source/security')
-rw-r--r--stoc/source/security/access_controller.cxx863
-rw-r--r--stoc/source/security/file_policy.cxx493
-rw-r--r--stoc/source/security/lru_cache.h210
-rw-r--r--stoc/source/security/permissions.cxx604
-rw-r--r--stoc/source/security/permissions.h85
5 files changed, 2255 insertions, 0 deletions
diff --git a/stoc/source/security/access_controller.cxx b/stoc/source/security/access_controller.cxx
new file mode 100644
index 000000000..c96f1f61f
--- /dev/null
+++ b/stoc/source/security/access_controller.cxx
@@ -0,0 +1,863 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+
+#include <vector>
+
+#include <osl/diagnose.h>
+#include <osl/mutex.hxx>
+#include <osl/thread.hxx>
+
+#include <rtl/ustrbuf.hxx>
+#include <sal/log.hxx>
+
+#include <uno/current_context.h>
+#include <uno/lbnames.h>
+
+#include <cppuhelper/basemutex.hxx>
+#include <cppuhelper/implbase.hxx>
+#include <cppuhelper/compbase.hxx>
+#include <cppuhelper/supportsservice.hxx>
+
+#include <com/sun/star/uno/XCurrentContext.hpp>
+#include <com/sun/star/uno/DeploymentException.hpp>
+#include <com/sun/star/uno/XComponentContext.hpp>
+#include <com/sun/star/lang/DisposedException.hpp>
+#include <com/sun/star/lang/XServiceInfo.hpp>
+#include <com/sun/star/lang/XInitialization.hpp>
+#include <com/sun/star/security/XAccessController.hpp>
+#include <com/sun/star/security/XPolicy.hpp>
+
+#include "lru_cache.h"
+#include "permissions.h"
+
+#include <memory>
+
+constexpr OUStringLiteral SERVICE_NAME = u"com.sun.star.security.AccessController";
+constexpr OUStringLiteral USER_CREDS = u"access-control.user-credentials.id";
+
+
+using namespace ::std;
+using namespace ::osl;
+using namespace ::cppu;
+using namespace ::com::sun::star;
+using namespace css::uno;
+using namespace stoc_sec;
+
+namespace {
+
+// static stuff initialized when loading lib
+OUString s_envType = CPPU_CURRENT_LANGUAGE_BINDING_NAME;
+constexpr OUStringLiteral s_acRestriction = u"access-control.restriction";
+
+
+/** ac context intersects permissions of two ac contexts
+*/
+class acc_Intersection
+ : public WeakImplHelper< security::XAccessControlContext >
+{
+ Reference< security::XAccessControlContext > m_x1, m_x2;
+
+ acc_Intersection(
+ Reference< security::XAccessControlContext > const & x1,
+ Reference< security::XAccessControlContext > const & x2 );
+
+public:
+ static Reference< security::XAccessControlContext > create(
+ Reference< security::XAccessControlContext > const & x1,
+ Reference< security::XAccessControlContext > const & x2 );
+
+ // XAccessControlContext impl
+ virtual void SAL_CALL checkPermission(
+ Any const & perm ) override;
+};
+
+acc_Intersection::acc_Intersection(
+ Reference< security::XAccessControlContext > const & x1,
+ Reference< security::XAccessControlContext > const & x2 )
+ : m_x1( x1 )
+ , m_x2( x2 )
+{}
+
+Reference< security::XAccessControlContext > acc_Intersection::create(
+ Reference< security::XAccessControlContext > const & x1,
+ Reference< security::XAccessControlContext > const & x2 )
+{
+ if (! x1.is())
+ return x2;
+ if (! x2.is())
+ return x1;
+ return new acc_Intersection( x1, x2 );
+}
+
+void acc_Intersection::checkPermission(
+ Any const & perm )
+{
+ m_x1->checkPermission( perm );
+ m_x2->checkPermission( perm );
+}
+
+/** ac context unifies permissions of two ac contexts
+*/
+class acc_Union
+ : public WeakImplHelper< security::XAccessControlContext >
+{
+ Reference< security::XAccessControlContext > m_x1, m_x2;
+
+ acc_Union(
+ Reference< security::XAccessControlContext > const & x1,
+ Reference< security::XAccessControlContext > const & x2 );
+
+public:
+ static Reference< security::XAccessControlContext > create(
+ Reference< security::XAccessControlContext > const & x1,
+ Reference< security::XAccessControlContext > const & x2 );
+
+ // XAccessControlContext impl
+ virtual void SAL_CALL checkPermission(
+ Any const & perm ) override;
+};
+
+acc_Union::acc_Union(
+ Reference< security::XAccessControlContext > const & x1,
+ Reference< security::XAccessControlContext > const & x2 )
+ : m_x1( x1 )
+ , m_x2( x2 )
+{}
+
+Reference< security::XAccessControlContext > acc_Union::create(
+ Reference< security::XAccessControlContext > const & x1,
+ Reference< security::XAccessControlContext > const & x2 )
+{
+ if (! x1.is())
+ return Reference< security::XAccessControlContext >(); // unrestricted
+ if (! x2.is())
+ return Reference< security::XAccessControlContext >(); // unrestricted
+ return new acc_Union( x1, x2 );
+}
+
+void acc_Union::checkPermission(
+ Any const & perm )
+{
+ try
+ {
+ m_x1->checkPermission( perm );
+ }
+ catch (security::AccessControlException &)
+ {
+ m_x2->checkPermission( perm );
+ }
+}
+
+/** ac context doing permission checks on static permissions
+*/
+class acc_Policy
+ : public WeakImplHelper< security::XAccessControlContext >
+{
+ PermissionCollection m_permissions;
+
+public:
+ explicit acc_Policy(
+ PermissionCollection const & permissions )
+ : m_permissions( permissions )
+ {}
+
+ // XAccessControlContext impl
+ virtual void SAL_CALL checkPermission(
+ Any const & perm ) override;
+};
+
+void acc_Policy::checkPermission(
+ Any const & perm )
+{
+ m_permissions.checkPermission( perm );
+}
+
+/** current context overriding dynamic ac restriction
+*/
+class acc_CurrentContext
+ : public WeakImplHelper< XCurrentContext >
+{
+ Reference< XCurrentContext > m_xDelegate;
+ Any m_restriction;
+
+public:
+ acc_CurrentContext(
+ Reference< XCurrentContext > const & xDelegate,
+ Reference< security::XAccessControlContext > const & xRestriction );
+
+ // XCurrentContext impl
+ virtual Any SAL_CALL getValueByName( OUString const & name ) override;
+};
+
+acc_CurrentContext::acc_CurrentContext(
+ Reference< XCurrentContext > const & xDelegate,
+ Reference< security::XAccessControlContext > const & xRestriction )
+ : m_xDelegate( xDelegate )
+{
+ if (xRestriction.is())
+ {
+ m_restriction <<= xRestriction;
+ }
+ // return empty any otherwise on getValueByName(), not null interface
+}
+
+Any acc_CurrentContext::getValueByName( OUString const & name )
+{
+ if (name == s_acRestriction)
+ {
+ return m_restriction;
+ }
+ else if (m_xDelegate.is())
+ {
+ return m_xDelegate->getValueByName( name );
+ }
+ else
+ {
+ return Any();
+ }
+}
+
+
+Reference< security::XAccessControlContext > getDynamicRestriction(
+ Reference< XCurrentContext > const & xContext )
+{
+ if (xContext.is())
+ {
+ Any acc(xContext->getValueByName(s_acRestriction));
+ if (typelib_TypeClass_INTERFACE == acc.pType->eTypeClass)
+ {
+ // avoid ref-counting
+ OUString const & typeName =
+ OUString::unacquired( &acc.pType->pTypeName );
+ if ( typeName == "com.sun.star.security.XAccessControlContext" )
+ {
+ return Reference< security::XAccessControlContext >(
+ *static_cast< security::XAccessControlContext ** >( acc.pData ) );
+ }
+ else // try to query
+ {
+ return Reference< security::XAccessControlContext >::query(
+ *static_cast< XInterface ** >( acc.pData ) );
+ }
+ }
+ }
+ return Reference< security::XAccessControlContext >();
+}
+
+class cc_reset
+{
+ void * m_cc;
+public:
+ explicit cc_reset( void * cc )
+ : m_cc( cc ) {}
+ ~cc_reset()
+ { ::uno_setCurrentContext( m_cc, s_envType.pData, nullptr ); }
+};
+
+typedef WeakComponentImplHelper<
+ security::XAccessController, lang::XServiceInfo, lang::XInitialization > t_helper;
+
+
+class AccessController
+ : public cppu::BaseMutex
+ , public t_helper
+{
+ Reference< XComponentContext > m_xComponentContext;
+
+ Reference< security::XPolicy > m_xPolicy;
+ Reference< security::XPolicy > const & getPolicy();
+
+ // mode
+ enum class Mode { Off, On, DynamicOnly, SingleUser, SingleDefaultUser };
+ Mode m_mode;
+
+ PermissionCollection m_defaultPermissions;
+ // for single-user mode
+ PermissionCollection m_singleUserPermissions;
+ OUString m_singleUserId;
+ bool m_defaultPerm_init;
+ bool m_singleUser_init;
+ // for multi-user mode
+ lru_cache< OUString, PermissionCollection, OUStringHash, equal_to< OUString > >
+ m_user2permissions;
+
+ ThreadData m_rec;
+ typedef vector< pair< OUString, Any > > t_rec_vec;
+ void clearPostPoned();
+ void checkAndClearPostPoned();
+
+ PermissionCollection getEffectivePermissions(
+ Reference< XCurrentContext > const & xContext,
+ Any const & demanded_perm );
+
+protected:
+ virtual void SAL_CALL disposing() override;
+
+public:
+ explicit AccessController( Reference< XComponentContext > const & xComponentContext );
+
+ // XInitialization impl
+ virtual void SAL_CALL initialize(
+ Sequence< Any > const & arguments ) override;
+
+ // XAccessController impl
+ virtual void SAL_CALL checkPermission(
+ Any const & perm ) override;
+ virtual Any SAL_CALL doRestricted(
+ Reference< security::XAction > const & xAction,
+ Reference< security::XAccessControlContext > const & xRestriction ) override;
+ virtual Any SAL_CALL doPrivileged(
+ Reference< security::XAction > const & xAction,
+ Reference< security::XAccessControlContext > const & xRestriction ) override;
+ virtual Reference< security::XAccessControlContext > SAL_CALL getContext() override;
+
+ // XServiceInfo impl
+ virtual OUString SAL_CALL getImplementationName() override;
+ virtual sal_Bool SAL_CALL supportsService( OUString const & serviceName ) override;
+ virtual Sequence< OUString > SAL_CALL getSupportedServiceNames() override;
+};
+
+AccessController::AccessController( Reference< XComponentContext > const & xComponentContext )
+ : t_helper( m_aMutex )
+ , m_xComponentContext( xComponentContext )
+ , m_mode( Mode::On ) // default
+ , m_defaultPerm_init( false )
+ , m_singleUser_init( false )
+ , m_rec( nullptr )
+{
+ // The .../mode value had originally been set in
+ // cppu::add_access_control_entries (cppuhelper/source/servicefactory.cxx)
+ // to something other than "off" depending on various UNO_AC* bootstrap
+ // variables that are no longer supported, so this is mostly dead code now:
+ OUString mode;
+ if (m_xComponentContext->getValueByName( "/services/" + SERVICE_NAME + "/mode" ) >>= mode)
+ {
+ if ( mode == "off" )
+ {
+ m_mode = Mode::Off;
+ }
+ else if ( mode == "on" )
+ {
+ m_mode = Mode::On;
+ }
+ else if ( mode == "dynamic-only" )
+ {
+ m_mode = Mode::DynamicOnly;
+ }
+ else if ( mode == "single-user" )
+ {
+ m_xComponentContext->getValueByName(
+ "/services/" + SERVICE_NAME + "/single-user-id" ) >>= m_singleUserId;
+ if (m_singleUserId.isEmpty())
+ {
+ throw RuntimeException(
+ "expected a user id in component context entry "
+ "\"/services/" + SERVICE_NAME + "/single-user-id\"!",
+ static_cast<OWeakObject *>(this) );
+ }
+ m_mode = Mode::SingleUser;
+ }
+ else if ( mode == "single-default-user" )
+ {
+ m_mode = Mode::SingleDefaultUser;
+ }
+ }
+
+ // switch on caching for Mode::DynamicOnly and Mode::On (shareable multi-user process)
+ if (Mode::On != m_mode && Mode::DynamicOnly != m_mode)
+ return;
+
+ sal_Int32 cacheSize = 0; // multi-user cache size
+ if (! (m_xComponentContext->getValueByName(
+ "/services/" + SERVICE_NAME + "/user-cache-size" ) >>= cacheSize))
+ {
+ cacheSize = 128; // reasonable default?
+ }
+#ifdef __CACHE_DIAGNOSE
+ cacheSize = 2;
+#endif
+ m_user2permissions.setSize( cacheSize );
+}
+
+void AccessController::disposing()
+{
+ m_mode = Mode::Off; // avoid checks from now on xxx todo review/ better Mode::DynamicOnly?
+ m_xPolicy.clear();
+ m_xComponentContext.clear();
+}
+
+// XInitialization impl
+
+void AccessController::initialize(
+ Sequence< Any > const & arguments )
+{
+ // xxx todo: review for forking
+ // portal forking hack: re-initialize for another user-id
+ if (Mode::SingleUser != m_mode) // only if in single-user mode
+ {
+ throw RuntimeException(
+ "invalid call: ac must be in \"single-user\" mode!", static_cast<OWeakObject *>(this) );
+ }
+ OUString userId;
+ arguments[ 0 ] >>= userId;
+ if ( userId.isEmpty() )
+ {
+ throw RuntimeException(
+ "expected a user-id as first argument!", static_cast<OWeakObject *>(this) );
+ }
+ // assured that no sync is necessary: no check happens at this forking time
+ m_singleUserId = userId;
+ m_singleUser_init = false;
+}
+
+
+Reference< security::XPolicy > const & AccessController::getPolicy()
+{
+ // get policy singleton
+ if (! m_xPolicy.is())
+ {
+ Reference< security::XPolicy > xPolicy;
+ m_xComponentContext->getValueByName(
+ "/singletons/com.sun.star.security.thePolicy" ) >>= xPolicy;
+ if (!xPolicy.is())
+ {
+ throw SecurityException(
+ "cannot get policy singleton!", static_cast<OWeakObject *>(this) );
+ }
+
+ MutexGuard guard( m_aMutex );
+ if (! m_xPolicy.is())
+ {
+ m_xPolicy = xPolicy;
+ }
+ }
+ return m_xPolicy;
+}
+
+#ifdef __DIAGNOSE
+static void dumpPermissions(
+ PermissionCollection const & collection, OUString const & userId = OUString() )
+{
+ OUStringBuffer buf( 48 );
+ if (!userId.isEmpty())
+ {
+ buf.append( "> dumping permissions of user \"" );
+ buf.append( userId );
+ buf.append( "\":" );
+ }
+ else
+ {
+ buf.append( "> dumping default permissions:" );
+ }
+ SAL_INFO("stoc", buf.makeStringAndClear() );
+ Sequence< OUString > permissions( collection.toStrings() );
+ OUString const * p = permissions.getConstArray();
+ for ( sal_Int32 nPos = 0; nPos < permissions.getLength(); ++nPos )
+ {
+ SAL_INFO("stoc", p[ nPos ] );
+ }
+ SAL_INFO("stoc", "> permission dump done" );
+}
+#endif
+
+
+void AccessController::clearPostPoned()
+{
+ delete static_cast< t_rec_vec * >( m_rec.getData() );
+ m_rec.setData( nullptr );
+}
+
+void AccessController::checkAndClearPostPoned()
+{
+ // check postponed permissions
+ std::unique_ptr< t_rec_vec > rec( static_cast< t_rec_vec * >( m_rec.getData() ) );
+ m_rec.setData( nullptr ); // takeover ownership
+ OSL_ASSERT(rec);
+ if (!rec)
+ return;
+
+ t_rec_vec const& vec = *rec;
+ switch (m_mode)
+ {
+ case Mode::SingleUser:
+ {
+ OSL_ASSERT( m_singleUser_init );
+ for (const auto & p : vec)
+ {
+ OSL_ASSERT( m_singleUserId == p.first );
+ m_singleUserPermissions.checkPermission( p.second );
+ }
+ break;
+ }
+ case Mode::SingleDefaultUser:
+ {
+ OSL_ASSERT( m_defaultPerm_init );
+ for (const auto & p : vec)
+ {
+ OSL_ASSERT( p.first.isEmpty() ); // default-user
+ m_defaultPermissions.checkPermission( p.second );
+ }
+ break;
+ }
+ case Mode::On:
+ {
+ for (const auto & p : vec)
+ {
+ PermissionCollection const * pPermissions;
+ // lookup policy for user
+ {
+ MutexGuard guard( m_aMutex );
+ pPermissions = m_user2permissions.lookup( p.first );
+ }
+ OSL_ASSERT( pPermissions );
+ if (pPermissions)
+ {
+ pPermissions->checkPermission( p.second );
+ }
+ }
+ break;
+ }
+ default:
+ OSL_FAIL( "### this should never be called in this ac mode!" );
+ break;
+ }
+}
+
+/** this is the only function calling the policy singleton and thus has to take care
+ of recurring calls!
+
+ @param demanded_perm (if not empty) is the demanded permission of a checkPermission() call
+ which will be postponed for recurring calls
+*/
+PermissionCollection AccessController::getEffectivePermissions(
+ Reference< XCurrentContext > const & xContext,
+ Any const & demanded_perm )
+{
+ OUString userId;
+
+ switch (m_mode)
+ {
+ case Mode::SingleUser:
+ {
+ if (m_singleUser_init)
+ return m_singleUserPermissions;
+ userId = m_singleUserId;
+ break;
+ }
+ case Mode::SingleDefaultUser:
+ {
+ if (m_defaultPerm_init)
+ return m_defaultPermissions;
+ break;
+ }
+ case Mode::On:
+ {
+ if (xContext.is())
+ {
+ xContext->getValueByName( USER_CREDS ) >>= userId;
+ }
+ if ( userId.isEmpty() )
+ {
+ throw SecurityException(
+ "cannot determine current user in multi-user ac!", static_cast<OWeakObject *>(this) );
+ }
+
+ // lookup policy for user
+ MutexGuard guard( m_aMutex );
+ PermissionCollection const * pPermissions = m_user2permissions.lookup( userId );
+ if (pPermissions)
+ return *pPermissions;
+ break;
+ }
+ default:
+ OSL_FAIL( "### this should never be called in this ac mode!" );
+ return PermissionCollection();
+ }
+
+ // call on policy
+ // iff this is a recurring call for the default user, then grant all permissions
+ t_rec_vec * rec = static_cast< t_rec_vec * >( m_rec.getData() );
+ if (rec) // tls entry exists => this is recursive call
+ {
+ if (demanded_perm.hasValue())
+ {
+ // enqueue
+ rec->push_back( pair< OUString, Any >( userId, demanded_perm ) );
+ }
+#ifdef __DIAGNOSE
+ SAL_INFO("stoc", "> info: recurring call of user: " << userId );
+#endif
+ return PermissionCollection( new AllPermission() );
+ }
+ else // no tls
+ {
+ rec = new t_rec_vec;
+ m_rec.setData( rec );
+ }
+
+ try // calls on API
+ {
+ // init default permissions
+ if (! m_defaultPerm_init)
+ {
+ PermissionCollection defaultPermissions(
+ getPolicy()->getDefaultPermissions() );
+ // assign
+ MutexGuard guard( m_aMutex );
+ if (! m_defaultPerm_init)
+ {
+ m_defaultPermissions = defaultPermissions;
+ m_defaultPerm_init = true;
+ }
+#ifdef __DIAGNOSE
+ dumpPermissions( m_defaultPermissions );
+#endif
+ }
+
+ PermissionCollection ret;
+
+ // init user permissions
+ switch (m_mode)
+ {
+ case Mode::SingleUser:
+ {
+ ret = PermissionCollection(
+ getPolicy()->getPermissions( userId ), m_defaultPermissions );
+ {
+ // assign
+ MutexGuard guard( m_aMutex );
+ if (m_singleUser_init)
+ {
+ ret = m_singleUserPermissions;
+ }
+ else
+ {
+ m_singleUserPermissions = ret;
+ m_singleUser_init = true;
+ }
+ }
+#ifdef __DIAGNOSE
+ dumpPermissions( ret, userId );
+#endif
+ break;
+ }
+ case Mode::SingleDefaultUser:
+ {
+ ret = m_defaultPermissions;
+ break;
+ }
+ case Mode::On:
+ {
+ ret = PermissionCollection(
+ getPolicy()->getPermissions( userId ), m_defaultPermissions );
+ {
+ // cache
+ MutexGuard guard( m_aMutex );
+ m_user2permissions.set( userId, ret );
+ }
+#ifdef __DIAGNOSE
+ dumpPermissions( ret, userId );
+#endif
+ break;
+ }
+ default:
+ break;
+ }
+
+ // check postponed
+ checkAndClearPostPoned();
+ return ret;
+ }
+ catch (const security::AccessControlException & exc) // wrapped into DeploymentException
+ {
+ clearPostPoned(); // safety: exception could have happened before checking postponed?
+ throw DeploymentException( "deployment error (AccessControlException occurred): " + exc.Message, exc.Context );
+ }
+ catch (RuntimeException &)
+ {
+ // don't check postponed, just cleanup
+ clearPostPoned();
+ delete static_cast< t_rec_vec * >( m_rec.getData() );
+ m_rec.setData( nullptr );
+ throw;
+ }
+ catch (Exception &)
+ {
+ // check postponed permissions first
+ // => AccessControlExceptions are errors, user exceptions not!
+ checkAndClearPostPoned();
+ throw;
+ }
+ catch (...)
+ {
+ // don't check postponed, just cleanup
+ clearPostPoned();
+ throw;
+ }
+}
+
+// XAccessController impl
+
+void AccessController::checkPermission(
+ Any const & perm )
+{
+ if (rBHelper.bDisposed)
+ {
+ throw lang::DisposedException(
+ "checkPermission() call on disposed AccessController!", static_cast<OWeakObject *>(this) );
+ }
+
+ if (Mode::Off == m_mode)
+ return;
+
+ // first dynamic check of ac contexts
+ Reference< XCurrentContext > xContext;
+ ::uno_getCurrentContext( reinterpret_cast<void **>(&xContext), s_envType.pData, nullptr );
+ Reference< security::XAccessControlContext > xACC( getDynamicRestriction( xContext ) );
+ if (xACC.is())
+ {
+ xACC->checkPermission( perm );
+ }
+
+ if (Mode::DynamicOnly == m_mode)
+ return;
+
+ // then static check
+ getEffectivePermissions( xContext, perm ).checkPermission( perm );
+}
+
+Any AccessController::doRestricted(
+ Reference< security::XAction > const & xAction,
+ Reference< security::XAccessControlContext > const & xRestriction )
+{
+ if (rBHelper.bDisposed)
+ {
+ throw lang::DisposedException(
+ "doRestricted() call on disposed AccessController!", static_cast<OWeakObject *>(this) );
+ }
+
+ if (Mode::Off == m_mode) // optimize this way, because no dynamic check will be performed
+ return xAction->run();
+
+ if (xRestriction.is())
+ {
+ Reference< XCurrentContext > xContext;
+ ::uno_getCurrentContext( reinterpret_cast<void **>(&xContext), s_envType.pData, nullptr );
+
+ // override restriction
+ Reference< XCurrentContext > xNewContext(
+ new acc_CurrentContext( xContext, acc_Intersection::create(
+ xRestriction, getDynamicRestriction( xContext ) ) ) );
+ ::uno_setCurrentContext( xNewContext.get(), s_envType.pData, nullptr );
+ cc_reset reset( xContext.get() );
+ return xAction->run();
+ }
+ else
+ {
+ return xAction->run();
+ }
+}
+
+Any AccessController::doPrivileged(
+ Reference< security::XAction > const & xAction,
+ Reference< security::XAccessControlContext > const & xRestriction )
+{
+ if (rBHelper.bDisposed)
+ {
+ throw lang::DisposedException(
+ "doPrivileged() call on disposed AccessController!", static_cast<OWeakObject *>(this) );
+ }
+
+ if (Mode::Off == m_mode) // no dynamic check will be performed
+ {
+ return xAction->run();
+ }
+
+ Reference< XCurrentContext > xContext;
+ ::uno_getCurrentContext( reinterpret_cast<void **>(&xContext), s_envType.pData, nullptr );
+
+ Reference< security::XAccessControlContext > xOldRestr(
+ getDynamicRestriction( xContext ) );
+
+ if (xOldRestr.is()) // previous restriction
+ {
+ // override restriction
+ Reference< XCurrentContext > xNewContext(
+ new acc_CurrentContext( xContext, acc_Union::create( xRestriction, xOldRestr ) ) );
+ ::uno_setCurrentContext( xNewContext.get(), s_envType.pData, nullptr );
+ cc_reset reset( xContext.get() );
+ return xAction->run();
+ }
+ else // no previous restriction => never current restriction
+ {
+ return xAction->run();
+ }
+}
+
+Reference< security::XAccessControlContext > AccessController::getContext()
+{
+ if (rBHelper.bDisposed)
+ {
+ throw lang::DisposedException(
+ "getContext() call on disposed AccessController!", static_cast<OWeakObject *>(this) );
+ }
+
+ if (Mode::Off == m_mode) // optimize this way, because no dynamic check will be performed
+ {
+ return new acc_Policy( PermissionCollection( new AllPermission() ) );
+ }
+
+ Reference< XCurrentContext > xContext;
+ ::uno_getCurrentContext( reinterpret_cast<void **>(&xContext), s_envType.pData, nullptr );
+
+ return acc_Intersection::create(
+ getDynamicRestriction( xContext ),
+ new acc_Policy( getEffectivePermissions( xContext, Any() ) ) );
+}
+
+// XServiceInfo impl
+
+OUString AccessController::getImplementationName()
+{
+ return "com.sun.star.security.comp.stoc.AccessController";
+}
+
+sal_Bool AccessController::supportsService( OUString const & serviceName )
+{
+ return cppu::supportsService(this, serviceName);
+}
+
+Sequence< OUString > AccessController::getSupportedServiceNames()
+{
+ Sequence<OUString> aSNS { SERVICE_NAME };
+ return aSNS;
+}
+
+}
+
+extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface *
+com_sun_star_security_comp_stoc_AccessController_get_implementation(
+ css::uno::XComponentContext *context,
+ css::uno::Sequence<css::uno::Any> const &)
+{
+ return cppu::acquire(new AccessController(context));
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/stoc/source/security/file_policy.cxx b/stoc/source/security/file_policy.cxx
new file mode 100644
index 000000000..91f75092b
--- /dev/null
+++ b/stoc/source/security/file_policy.cxx
@@ -0,0 +1,493 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+
+#include <osl/diagnose.h>
+#include <osl/file.h>
+#include <rtl/byteseq.hxx>
+#include <rtl/ustrbuf.hxx>
+
+#include <cppuhelper/access_control.hxx>
+#include <cppuhelper/basemutex.hxx>
+#include <cppuhelper/compbase.hxx>
+#include <cppuhelper/supportsservice.hxx>
+
+#include <com/sun/star/lang/XServiceInfo.hpp>
+#include <com/sun/star/security/XPolicy.hpp>
+#include <com/sun/star/security/AllPermission.hpp>
+#include <com/sun/star/security/RuntimePermission.hpp>
+#include <com/sun/star/io/FilePermission.hpp>
+#include <com/sun/star/connection/SocketPermission.hpp>
+#include <com/sun/star/uno/XComponentContext.hpp>
+
+#include <string_view>
+#include <unordered_map>
+
+constexpr OUStringLiteral IMPL_NAME = u"com.sun.star.security.comp.stoc.FilePolicy";
+
+using namespace ::osl;
+using namespace ::cppu;
+using namespace ::com::sun::star;
+using namespace css::uno;
+
+namespace {
+
+typedef WeakComponentImplHelper< security::XPolicy, lang::XServiceInfo > t_helper;
+
+
+class FilePolicy
+ : public cppu::BaseMutex
+ , public t_helper
+{
+ Reference< XComponentContext > m_xComponentContext;
+ AccessControl m_ac;
+
+ Sequence< Any > m_defaultPermissions;
+ typedef std::unordered_map< OUString, Sequence< Any > > t_permissions;
+ t_permissions m_userPermissions;
+ bool m_init;
+
+protected:
+ virtual void SAL_CALL disposing() override;
+
+public:
+ explicit FilePolicy( Reference< XComponentContext > const & xComponentContext );
+
+ // XPolicy impl
+ virtual Sequence< Any > SAL_CALL getPermissions(
+ OUString const & userId ) override;
+ virtual Sequence< Any > SAL_CALL getDefaultPermissions() override;
+ virtual void SAL_CALL refresh() override;
+
+ // XServiceInfo impl
+ virtual OUString SAL_CALL getImplementationName() override;
+ virtual sal_Bool SAL_CALL supportsService( OUString const & serviceName ) override;
+ virtual Sequence< OUString > SAL_CALL getSupportedServiceNames() override;
+};
+
+FilePolicy::FilePolicy( Reference< XComponentContext > const & xComponentContext )
+ : t_helper( m_aMutex )
+ , m_xComponentContext( xComponentContext )
+ , m_ac( xComponentContext )
+ , m_init( false )
+{}
+
+void FilePolicy::disposing()
+{
+ m_userPermissions.clear();
+ m_defaultPermissions = Sequence< Any >();
+ m_xComponentContext.clear();
+}
+
+
+Sequence< Any > FilePolicy::getPermissions(
+ OUString const & userId )
+{
+ if (! m_init)
+ {
+ refresh();
+ m_init = true;
+ }
+
+ MutexGuard guard( m_aMutex );
+ t_permissions::iterator iFind( m_userPermissions.find( userId ) );
+ if (m_userPermissions.end() == iFind)
+ {
+ return Sequence< Any >();
+ }
+ else
+ {
+ return iFind->second;
+ }
+}
+
+Sequence< Any > FilePolicy::getDefaultPermissions()
+{
+ if (! m_init)
+ {
+ refresh();
+ m_init = true;
+ }
+
+ MutexGuard guard( m_aMutex );
+ return m_defaultPermissions;
+}
+
+
+class PolicyReader
+{
+ OUString m_fileName;
+ oslFileHandle m_file;
+
+ sal_Int32 m_linepos;
+ rtl::ByteSequence m_line;
+ sal_Int32 m_pos;
+ sal_Unicode m_back;
+
+ sal_Unicode get();
+ void back( sal_Unicode c )
+ { m_back = c; }
+
+ static bool isWhiteSpace( sal_Unicode c )
+ { return (' ' == c || '\t' == c || '\n' == c || '\r' == c); }
+ void skipWhiteSpace();
+
+ static bool isCharToken( sal_Unicode c )
+ { return (';' == c || ',' == c || '{' == c || '}' == c); }
+
+public:
+ PolicyReader( OUString const & file, AccessControl & ac );
+ ~PolicyReader();
+
+ void error( std::u16string_view msg );
+
+ OUString getToken();
+ OUString assureToken();
+ OUString getQuotedToken();
+ OUString assureQuotedToken();
+ void assureToken( sal_Unicode token );
+};
+
+void PolicyReader::assureToken( sal_Unicode token )
+{
+ skipWhiteSpace();
+ sal_Unicode c = get();
+ if (c == token)
+ return;
+ OUString msg = "expected >" + OUStringChar(c) + "<!";
+ error( msg );
+}
+
+OUString PolicyReader::assureQuotedToken()
+{
+ OUString token( getQuotedToken() );
+ if (token.isEmpty())
+ error( u"unexpected end of file!" );
+ return token;
+}
+
+OUString PolicyReader::getQuotedToken()
+{
+ skipWhiteSpace();
+ OUStringBuffer buf( 32 );
+ sal_Unicode c = get();
+ if ('\"' != c)
+ error( u"expected quoting >\"< character!" );
+ c = get();
+ while ('\0' != c && '\"' != c)
+ {
+ buf.append( c );
+ c = get();
+ }
+ return buf.makeStringAndClear();
+}
+
+OUString PolicyReader::assureToken()
+{
+ OUString token( getToken() );
+ if ( token.isEmpty())
+ error( u"unexpected end of file!" );
+ return token;
+}
+
+OUString PolicyReader::getToken()
+{
+ skipWhiteSpace();
+ sal_Unicode c = get();
+ if (isCharToken( c ))
+ return OUString( &c, 1 );
+ OUStringBuffer buf( 32 );
+ while ('\0' != c && !isCharToken( c ) && !isWhiteSpace( c ))
+ {
+ buf.append( c );
+ c = get();
+ }
+ back( c );
+ return buf.makeStringAndClear();
+}
+
+void PolicyReader::skipWhiteSpace()
+{
+ sal_Unicode c;
+ do
+ {
+ c = get();
+ }
+ while (isWhiteSpace( c )); // seeking next non-whitespace char
+
+ if ('/' == c) // C/C++ like comment
+ {
+ c = get();
+ if ('/' == c) // C++ like comment
+ {
+ do
+ {
+ c = get();
+ }
+ while ('\n' != c && '\0' != c); // seek eol/eof
+ skipWhiteSpace(); // cont skip on next line
+ }
+ else if ('*' == c) // C like comment
+ {
+ bool fini = true;
+ do
+ {
+ c = get();
+ if ('*' == c)
+ {
+ c = get();
+ fini = ('/' == c || '\0' == c);
+ }
+ else
+ {
+ fini = ('\0' == c);
+ }
+ }
+ while (! fini);
+ skipWhiteSpace(); // cont skip on next line
+ }
+ else
+ {
+ error( u"expected C/C++ like comment!" );
+ }
+ }
+ else if ('#' == c) // script like comment
+ {
+ do
+ {
+ c = get();
+ }
+ while ('\n' != c && '\0' != c); // seek eol/eof
+ skipWhiteSpace(); // cont skip on next line
+ }
+
+ else // is token char
+ {
+ back( c );
+ }
+}
+
+sal_Unicode PolicyReader::get()
+{
+ if ('\0' != m_back) // one char push back possible
+ {
+ sal_Unicode c = m_back;
+ m_back = '\0';
+ return c;
+ }
+ else if (m_pos == m_line.getLength()) // provide newline as whitespace
+ {
+ ++m_pos;
+ return '\n';
+ }
+ else if (m_pos > m_line.getLength()) // read new line
+ {
+ sal_Bool eof;
+ oslFileError rc = ::osl_isEndOfFile( m_file, &eof );
+ if (osl_File_E_None != rc)
+ error( u"checking eof failed!" );
+ if (eof)
+ return '\0';
+
+ rc = ::osl_readLine( m_file, reinterpret_cast< sal_Sequence ** >( &m_line ) );
+ if (osl_File_E_None != rc)
+ error( u"read line failed!" );
+ ++m_linepos;
+ if (! m_line.getLength()) // empty line read
+ {
+ m_pos = 1; // read new line next time
+ return '\n';
+ }
+ m_pos = 0;
+ }
+ return (m_line.getConstArray()[ m_pos++ ]);
+}
+
+void PolicyReader::error( std::u16string_view msg )
+{
+ throw RuntimeException(
+ "error processing file \"" + m_fileName +
+ "\" [line " + OUString::number(m_linepos) +
+ ", column " + OUString::number(m_pos) +
+ "] " + msg);
+}
+
+PolicyReader::PolicyReader( OUString const & fileName, AccessControl & ac )
+ : m_fileName( fileName )
+ , m_linepos( 0 )
+ , m_pos( 1 ) // force readline
+ , m_back( '\0' )
+{
+ ac.checkFilePermission( m_fileName, "read" );
+ if (osl_File_E_None != ::osl_openFile( m_fileName.pData, &m_file, osl_File_OpenFlag_Read ))
+ {
+ throw RuntimeException( "cannot open file \"" + m_fileName + "\"!" );
+ }
+}
+
+PolicyReader::~PolicyReader()
+{
+ if ( ::osl_closeFile( m_file ) != osl_File_E_None ) {
+ OSL_ASSERT( false );
+ }
+}
+
+constexpr OUStringLiteral s_grant = u"grant";
+constexpr OUStringLiteral s_user = u"user";
+constexpr OUStringLiteral s_permission = u"permission";
+constexpr OUStringLiteral s_openBrace = u"{";
+constexpr OUStringLiteral s_closingBrace = u"}";
+
+constexpr OUStringLiteral s_filePermission = u"com.sun.star.io.FilePermission";
+constexpr OUStringLiteral s_socketPermission = u"com.sun.star.connection.SocketPermission";
+constexpr OUStringLiteral s_runtimePermission = u"com.sun.star.security.RuntimePermission";
+constexpr OUStringLiteral s_allPermission = u"com.sun.star.security.AllPermission";
+
+
+void FilePolicy::refresh()
+{
+ // read out file (the .../file-name value had originally been set in
+ // cppu::add_access_control_entries (cppuhelper/source/servicefactory.cxx)
+ // depending on various UNO_AC* bootstrap variables that are no longer
+ // supported, so this is effectively dead code):
+ OUString fileName;
+ m_xComponentContext->getValueByName(
+ "/implementations/" + IMPL_NAME + "/file-name" ) >>= fileName;
+ if ( fileName.isEmpty() )
+ {
+ throw RuntimeException(
+ "name of policy file unknown!",
+ static_cast<OWeakObject *>(this) );
+ }
+
+ PolicyReader reader( fileName, m_ac );
+
+ // fill these two
+ Sequence< Any > defaultPermissions;
+ t_permissions userPermissions;
+
+ OUString token( reader.getToken() );
+ while (!token.isEmpty())
+ {
+ if ( token != s_grant )
+ reader.error( u"expected >grant< token!" );
+ OUString userId;
+ token = reader.assureToken();
+ if ( token == s_user ) // next token is user-id
+ {
+ userId = reader.assureQuotedToken();
+ token = reader.assureToken();
+ }
+ if ( token != s_openBrace )
+ reader.error( u"expected opening brace >{<!" );
+ token = reader.assureToken();
+ // permissions list
+ while ( token != s_closingBrace )
+ {
+ if ( token != s_permission )
+ reader.error( u"expected >permission< or closing brace >}<!" );
+
+ token = reader.assureToken(); // permission type
+ Any perm;
+ if ( token == s_filePermission ) // FilePermission
+ {
+ OUString url( reader.assureQuotedToken() );
+ reader.assureToken( ',' );
+ OUString actions( reader.assureQuotedToken() );
+ perm <<= io::FilePermission( url, actions );
+ }
+ else if ( token == s_socketPermission ) // SocketPermission
+ {
+ OUString host( reader.assureQuotedToken() );
+ reader.assureToken( ',' );
+ OUString actions( reader.assureQuotedToken() );
+ perm <<= connection::SocketPermission( host, actions );
+ }
+ else if ( token == s_runtimePermission ) // RuntimePermission
+ {
+ OUString name( reader.assureQuotedToken() );
+ perm <<= security::RuntimePermission( name );
+ }
+ else if ( token == s_allPermission ) // AllPermission
+ {
+ perm <<= security::AllPermission();
+ }
+ else
+ {
+ reader.error( u"expected permission type!" );
+ }
+
+ reader.assureToken( ';' );
+
+ // insert
+ if (!userId.isEmpty())
+ {
+ Sequence< Any > perms( userPermissions[ userId ] );
+ sal_Int32 len = perms.getLength();
+ perms.realloc( len +1 );
+ perms.getArray()[ len ] = perm;
+ userPermissions[ userId ] = perms;
+ }
+ else
+ {
+ sal_Int32 len = defaultPermissions.getLength();
+ defaultPermissions.realloc( len +1 );
+ defaultPermissions.getArray()[ len ] = perm;
+ }
+
+ token = reader.assureToken(); // next permissions token
+ }
+
+ reader.assureToken( ';' ); // semi
+ token = reader.getToken(); // next grant token
+ }
+
+ // assign new ones
+ MutexGuard guard( m_aMutex );
+ m_defaultPermissions = defaultPermissions;
+ m_userPermissions = userPermissions;
+}
+
+
+OUString FilePolicy::getImplementationName()
+{
+ return IMPL_NAME;
+}
+
+sal_Bool FilePolicy::supportsService( OUString const & serviceName )
+{
+ return cppu::supportsService(this, serviceName);
+}
+
+Sequence< OUString > FilePolicy::getSupportedServiceNames()
+{
+ return { "com.sun.star.security.Policy" };
+}
+
+} // namespace
+
+extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface *
+com_sun_star_security_comp_stoc_FilePolicy_get_implementation(
+ css::uno::XComponentContext *context,
+ css::uno::Sequence<css::uno::Any> const &)
+{
+ return cppu::acquire(new FilePolicy(context));
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/stoc/source/security/lru_cache.h b/stoc/source/security/lru_cache.h
new file mode 100644
index 000000000..402b41d58
--- /dev/null
+++ b/stoc/source/security/lru_cache.h
@@ -0,0 +1,210 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+#ifndef INCLUDED_STOC_SOURCE_SECURITY_LRU_CACHE_H
+#define INCLUDED_STOC_SOURCE_SECURITY_LRU_CACHE_H
+
+#include <memory>
+#include <unordered_map>
+
+// __CACHE_DIAGNOSE works only for OUString keys
+#ifdef __CACHE_DIAGNOSE
+#include <osl/diagnose.h>
+#include <rtl/ustrbuf.hxx>
+#include <rtl/ustring.hxx>
+#include <rtl/string.hxx>
+#include <sal/log.hxx>
+#endif
+
+
+namespace stoc_sec
+{
+
+/** Implementation of a least recently used (lru) cache.
+*/
+template< typename t_key, typename t_val, typename t_hashKey, typename t_equalKey >
+class lru_cache
+{
+ struct Entry
+ {
+ t_key m_key;
+ t_val m_val;
+ Entry * m_pred;
+ Entry * m_succ;
+ };
+ typedef std::unordered_map< t_key, Entry *, t_hashKey, t_equalKey > t_key2element;
+ t_key2element m_key2element;
+ ::std::size_t m_size;
+
+ std::unique_ptr<Entry[]> m_block;
+ mutable Entry * m_head;
+ mutable Entry * m_tail;
+ inline void toFront( Entry * entry ) const;
+
+public:
+ /** Default Ctor. Does not cache.
+ */
+ inline lru_cache();
+
+ /** Retrieves a pointer to value in cache. Returns 0, if none was found.
+
+ @param key a key
+ @return pointer to value or 0
+ */
+ inline t_val const * lookup( t_key const & key ) const;
+
+ /** Sets a value to be cached for given key.
+
+ @param key a key
+ @param val a value
+ */
+ inline void set( t_key const & key, t_val const & val );
+
+ /** Sets the number of elements to be cached. This will clear previous entries.
+
+ @param cacheSize number of elements to be cached
+ */
+ inline void setSize( ::std::size_t size );
+};
+
+template< typename t_key, typename t_val, typename t_hashKey, typename t_equalKey >
+inline void lru_cache< t_key, t_val, t_hashKey, t_equalKey >::setSize(
+ ::std::size_t size )
+{
+ m_key2element.clear();
+ m_block.reset();
+ m_size = size;
+
+ if (0 < m_size)
+ {
+ m_block.reset( new Entry[ m_size ] );
+ m_head = m_block.get();
+ m_tail = m_block.get() + m_size -1;
+ for ( ::std::size_t nPos = m_size; nPos--; )
+ {
+ m_block[ nPos ].m_pred = m_block.get() + nPos -1;
+ m_block[ nPos ].m_succ = m_block.get() + nPos +1;
+ }
+ }
+}
+
+template< typename t_key, typename t_val, typename t_hashKey, typename t_equalKey >
+inline lru_cache< t_key, t_val, t_hashKey, t_equalKey >::lru_cache()
+ : m_size( 0 )
+ , m_block( nullptr )
+ , m_head( nullptr )
+ , m_tail( nullptr )
+{
+}
+
+template< typename t_key, typename t_val, typename t_hashKey, typename t_equalKey >
+inline void lru_cache< t_key, t_val, t_hashKey, t_equalKey >::toFront(
+ Entry * entry ) const
+{
+ if (entry != m_head)
+ {
+ // cut out element
+ if (entry == m_tail)
+ {
+ m_tail = entry->m_pred;
+ }
+ else
+ {
+ entry->m_succ->m_pred = entry->m_pred;
+ entry->m_pred->m_succ = entry->m_succ;
+ }
+ // push to front
+ m_head->m_pred = entry;
+ entry->m_succ = m_head;
+ m_head = entry;
+ }
+}
+
+template< typename t_key, typename t_val, typename t_hashKey, typename t_equalKey >
+inline t_val const * lru_cache< t_key, t_val, t_hashKey, t_equalKey >::lookup(
+ t_key const & key ) const
+{
+ if (0 < m_size)
+ {
+ typename t_key2element::const_iterator const iFind( m_key2element.find( key ) );
+ if (iFind != m_key2element.end())
+ {
+ Entry * entry = iFind->second;
+ toFront( entry );
+#ifdef __CACHE_DIAGNOSE
+ OUStringBuffer buf( 48 );
+ buf.appendAscii( "> retrieved element \"" );
+ buf.append( entry->m_key );
+ buf.appendAscii( "\" from cache" );
+ SAL_INFO("stoc", buf.makeStringAndClear() );
+#endif
+ return &entry->m_val;
+ }
+ }
+ return nullptr;
+}
+
+template< typename t_key, typename t_val, typename t_hashKey, typename t_equalKey >
+inline void lru_cache< t_key, t_val, t_hashKey, t_equalKey >::set(
+ t_key const & key, t_val const & val )
+{
+ if (0 < m_size)
+ {
+ typename t_key2element::const_iterator const iFind( m_key2element.find( key ) );
+
+ Entry * entry;
+ if (iFind == m_key2element.end())
+ {
+ entry = m_tail; // erase last element
+#ifdef __CACHE_DIAGNOSE
+ if (entry->m_key.getLength())
+ {
+ OUStringBuffer buf( 48 );
+ buf.appendAscii( "> kicking element \"" );
+ buf.append( entry->m_key );
+ buf.appendAscii( "\" from cache" );
+ SAL_INFO("stoc", buf.makeStringAndClear() );
+ }
+#endif
+ m_key2element.erase( entry->m_key );
+ entry->m_key = key;
+ ::std::pair< typename t_key2element::iterator, bool > insertion(
+ m_key2element.emplace( key, entry ) );
+ OSL_ENSURE( insertion.second, "### inserting new cache entry failed?!" );
+ }
+ else
+ {
+ entry = iFind->second;
+#ifdef __CACHE_DIAGNOSE
+ OUStringBuffer buf( 48 );
+ buf.appendAscii( "> replacing element \"" );
+ buf.append( entry->m_key );
+ buf.appendAscii( "\" in cache" );
+ SAL_INFO("stoc", buf.makeStringAndClear() );
+#endif
+ }
+ entry->m_val = val;
+ toFront( entry );
+ }
+}
+
+}
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/stoc/source/security/permissions.cxx b/stoc/source/security/permissions.cxx
new file mode 100644
index 000000000..1799f9c8c
--- /dev/null
+++ b/stoc/source/security/permissions.cxx
@@ -0,0 +1,604 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+
+#include <vector>
+
+#include <osl/process.h>
+#include <osl/socket.hxx>
+#include <osl/mutex.hxx>
+
+#include <rtl/string.hxx>
+#include <rtl/ustrbuf.hxx>
+#include <sal/log.hxx>
+#include <o3tl/string_view.hxx>
+
+#include <com/sun/star/security/RuntimePermission.hpp>
+#include <com/sun/star/security/AllPermission.hpp>
+#include <com/sun/star/io/FilePermission.hpp>
+#include <com/sun/star/connection/SocketPermission.hpp>
+#include <com/sun/star/security/AccessControlException.hpp>
+#include <com/sun/star/uno/Sequence.hxx>
+
+#include "permissions.h"
+
+using namespace ::std;
+using namespace ::osl;
+using namespace ::com::sun::star;
+using namespace css::uno;
+
+namespace stoc_sec
+{
+
+
+static sal_Int32 makeMask(
+ OUString const & items, char const * const * strings )
+{
+ sal_Int32 mask = 0;
+
+ sal_Int32 n = 0;
+ do
+ {
+ OUString item( o3tl::trim(o3tl::getToken(items, 0, ',', n )) );
+ if ( item.isEmpty())
+ continue;
+ sal_Int32 nPos = 0;
+ while (strings[ nPos ])
+ {
+ if (item.equalsAscii( strings[ nPos ] ))
+ {
+ mask |= (0x80000000 >> nPos);
+ break;
+ }
+ ++nPos;
+ }
+#if OSL_DEBUG_LEVEL > 0
+ if (! strings[ nPos ])
+ {
+ SAL_WARN("stoc", "ignoring unknown socket action: " << item );
+ }
+#endif
+ }
+ while (n >= 0); // all items
+ return mask;
+}
+
+static OUString makeStrings(
+ sal_Int32 mask, char const * const * strings )
+{
+ OUStringBuffer buf( 48 );
+ while (mask)
+ {
+ if (0x80000000 & mask)
+ {
+ buf.appendAscii( *strings );
+ if ((mask << 1) != 0) // more items following
+ buf.append( ',' );
+ }
+ mask = (mask << 1);
+ ++strings;
+ }
+ return buf.makeStringAndClear();
+}
+
+namespace {
+
+class SocketPermission : public Permission
+{
+ static char const * s_actions [];
+ sal_Int32 m_actions;
+
+ OUString m_host;
+ sal_Int32 m_lowerPort;
+ sal_Int32 m_upperPort;
+ mutable OUString m_ip;
+ mutable bool m_resolveErr;
+ mutable bool m_resolvedHost;
+ bool m_wildCardHost;
+
+ inline bool resolveHost() const;
+
+public:
+ SocketPermission(
+ connection::SocketPermission const & perm,
+ ::rtl::Reference< Permission > const & next = ::rtl::Reference< Permission >() );
+ virtual bool implies( Permission const & perm ) const override;
+ virtual OUString toString() const override;
+};
+
+}
+
+char const * SocketPermission::s_actions [] = { "accept", "connect", "listen", "resolve", nullptr };
+
+SocketPermission::SocketPermission(
+ connection::SocketPermission const & perm,
+ ::rtl::Reference< Permission > const & next )
+ : Permission( SOCKET, next )
+ , m_actions( makeMask( perm.Actions, s_actions ) )
+ , m_host( perm.Host )
+ , m_lowerPort( 0 )
+ , m_upperPort( 65535 )
+ , m_resolveErr( false )
+ , m_resolvedHost( false )
+ , m_wildCardHost( !perm.Host.isEmpty() && '*' == perm.Host.pData->buffer[ 0 ] )
+{
+ if (0xe0000000 & m_actions) // if any (except resolve) is given => resolve implied
+ m_actions |= 0x10000000;
+
+ // separate host from portrange
+ sal_Int32 colon = m_host.indexOf( ':' );
+ if (colon < 0) // port [range] not given
+ return;
+
+ sal_Int32 minus = m_host.indexOf( '-', colon +1 );
+ if (minus < 0)
+ {
+ m_lowerPort = m_upperPort = o3tl::toInt32(m_host.subView( colon +1 ));
+ }
+ else if (minus == (colon +1)) // -N
+ {
+ m_upperPort = o3tl::toInt32(m_host.subView( minus +1 ));
+ }
+ else if (minus == (m_host.getLength() -1)) // N-
+ {
+ m_lowerPort = o3tl::toInt32(m_host.subView( colon +1, m_host.getLength() -1 -colon -1 ));
+ }
+ else // A-B
+ {
+ m_lowerPort = o3tl::toInt32(m_host.subView( colon +1, minus - colon -1 ));
+ m_upperPort = o3tl::toInt32(m_host.subView( minus +1 ));
+ }
+ m_host = m_host.copy( 0, colon );
+}
+
+inline bool SocketPermission::resolveHost() const
+{
+ if (m_resolveErr)
+ return false;
+
+ if (! m_resolvedHost)
+ {
+ // dns lookup
+ SocketAddr addr;
+ SocketAddr::resolveHostname( m_host, addr );
+ OUString ip;
+ m_resolveErr = (::osl_Socket_Ok != ::osl_getDottedInetAddrOfSocketAddr(
+ addr.getHandle(), &ip.pData ));
+ if (m_resolveErr)
+ return false;
+
+ MutexGuard guard( Mutex::getGlobalMutex() );
+ if (! m_resolvedHost)
+ {
+ m_ip = ip;
+ m_resolvedHost = true;
+ }
+ }
+ return m_resolvedHost;
+}
+
+bool SocketPermission::implies( Permission const & perm ) const
+{
+ // check type
+ if (SOCKET != perm.m_type)
+ return false;
+ SocketPermission const & demanded = static_cast< SocketPermission const & >( perm );
+
+ // check actions
+ if ((m_actions & demanded.m_actions) != demanded.m_actions)
+ return false;
+
+ // check ports
+ if (demanded.m_lowerPort < m_lowerPort)
+ return false;
+ if (demanded.m_upperPort > m_upperPort)
+ return false;
+
+ // quick check host (DNS names: RFC 1034/1035)
+ if (m_host.equalsIgnoreAsciiCase( demanded.m_host ))
+ return true;
+ // check for host wildcards
+ if (m_wildCardHost)
+ {
+ OUString const & demanded_host = demanded.m_host;
+ if (demanded_host.getLength() <= m_host.getLength())
+ return false;
+ sal_Int32 len = m_host.getLength() -1; // skip star
+ return (0 == ::rtl_ustr_compareIgnoreAsciiCase_WithLength(
+ demanded_host.getStr() + demanded_host.getLength() - len, len,
+ m_host.pData->buffer + 1, len ));
+ }
+ if (demanded.m_wildCardHost)
+ return false;
+
+ // compare IP addresses
+ if (! resolveHost())
+ return false;
+ if (! demanded.resolveHost())
+ return false;
+ return m_ip == demanded.m_ip;
+}
+
+OUString SocketPermission::toString() const
+{
+ OUStringBuffer buf( 48 );
+ // host
+ buf.append( "com.sun.star.connection.SocketPermission (host=\"" );
+ buf.append( m_host );
+ if (m_resolvedHost)
+ {
+ buf.append( '[' );
+ buf.append( m_ip );
+ buf.append( ']' );
+ }
+ // port
+ if (0 != m_lowerPort || 65535 != m_upperPort)
+ {
+ buf.append( ':' );
+ if (m_lowerPort > 0)
+ buf.append( m_lowerPort );
+ if (m_upperPort > m_lowerPort)
+ {
+ buf.append( '-' );
+ if (m_upperPort < 65535)
+ buf.append( m_upperPort );
+ }
+ }
+ // actions
+ buf.append( "\", actions=\"" );
+ buf.append( makeStrings( m_actions, s_actions ) );
+ buf.append( "\")" );
+ return buf.makeStringAndClear();
+}
+
+namespace {
+
+class FilePermission : public Permission
+{
+ static char const * s_actions [];
+ sal_Int32 m_actions;
+
+ OUString m_url;
+ bool m_allFiles;
+
+public:
+ FilePermission(
+ io::FilePermission const & perm,
+ ::rtl::Reference< Permission > const & next = ::rtl::Reference< Permission >() );
+ virtual bool implies( Permission const & perm ) const override;
+ virtual OUString toString() const override;
+};
+
+}
+
+char const * FilePermission::s_actions [] = { "read", "write", "execute", "delete", nullptr };
+
+static OUString const & getWorkingDir()
+{
+ static OUString s_workingDir = []() {
+ OUString workingDir;
+ ::osl_getProcessWorkingDir(&workingDir.pData);
+ return workingDir;
+ }();
+ return s_workingDir;
+}
+
+FilePermission::FilePermission(
+ io::FilePermission const & perm,
+ ::rtl::Reference< Permission > const & next )
+ : Permission( FILE, next )
+ , m_actions( makeMask( perm.Actions, s_actions ) )
+ , m_url( perm.URL )
+ , m_allFiles( perm.URL == "<<ALL FILES>>" )
+{
+ if ( m_allFiles)
+ return;
+
+ if ( m_url == "*" )
+ {
+ m_url = getWorkingDir() + "/*";
+ }
+ else if ( m_url == "-" )
+ {
+ m_url = getWorkingDir() + "/-";
+ }
+ else if (!m_url.startsWith("file:///"))
+ {
+ // relative path
+ OUString out;
+ oslFileError rc = ::osl_getAbsoluteFileURL(
+ getWorkingDir().pData, perm.URL.pData, &out.pData );
+ m_url = (osl_File_E_None == rc ? out : perm.URL); // fallback
+ }
+#ifdef _WIN32
+ // correct win drive letters
+ if (9 < m_url.getLength() && '|' == m_url[ 9 ]) // file:///X|
+ {
+ constexpr OUStringLiteral s_colon = u":";
+ // common case in API is a ':' (sal), so convert '|' to ':'
+ m_url = m_url.replaceAt( 9, 1, s_colon );
+ }
+#endif
+}
+
+bool FilePermission::implies( Permission const & perm ) const
+{
+ // check type
+ if (FILE != perm.m_type)
+ return false;
+ FilePermission const & demanded = static_cast< FilePermission const & >( perm );
+
+ // check actions
+ if ((m_actions & demanded.m_actions) != demanded.m_actions)
+ return false;
+
+ // check url
+ if (m_allFiles)
+ return true;
+ if (demanded.m_allFiles)
+ return false;
+
+#ifdef _WIN32
+ if (m_url.equalsIgnoreAsciiCase( demanded.m_url ))
+ return true;
+#else
+ if (m_url == demanded.m_url )
+ return true;
+#endif
+ if (m_url.getLength() > demanded.m_url.getLength())
+ return false;
+ // check /- wildcard: all files and recursive in that path
+ if (m_url.endsWith("/-"))
+ {
+ // demanded url must start with granted path (including path trailing path sep)
+ sal_Int32 len = m_url.getLength() -1;
+#ifdef _WIN32
+ return (0 == ::rtl_ustr_compareIgnoreAsciiCase_WithLength(
+ demanded.m_url.pData->buffer, len, m_url.pData->buffer, len ));
+#else
+ return (0 == ::rtl_ustr_reverseCompare_WithLength(
+ demanded.m_url.pData->buffer, len, m_url.pData->buffer, len ));
+#endif
+ }
+ // check /* wildcard: all files in that path (not recursive!)
+ if (m_url.endsWith("/*"))
+ {
+ // demanded url must start with granted path (including path trailing path sep)
+ sal_Int32 len = m_url.getLength() -1;
+#ifdef _WIN32
+ return ((0 == ::rtl_ustr_compareIgnoreAsciiCase_WithLength(
+ demanded.m_url.pData->buffer, len, m_url.pData->buffer, len )) &&
+ (0 > demanded.m_url.indexOf( '/', len ))); // in addition, no deeper paths
+#else
+ return ((0 == ::rtl_ustr_reverseCompare_WithLength(
+ demanded.m_url.pData->buffer, len, m_url.pData->buffer, len )) &&
+ (0 > demanded.m_url.indexOf( '/', len ))); // in addition, no deeper paths
+#endif
+ }
+ return false;
+}
+
+OUString FilePermission::toString() const
+{
+ return
+ // url
+ "com.sun.star.io.FilePermission (url=\"" + m_url
+ // actions
+ + "\", actions=\"" + makeStrings( m_actions, s_actions ) + "\")";
+}
+
+namespace {
+
+class RuntimePermission : public Permission
+{
+ OUString m_name;
+
+public:
+ RuntimePermission(
+ security::RuntimePermission const & perm,
+ ::rtl::Reference< Permission > const & next = ::rtl::Reference< Permission >() )
+ : Permission( RUNTIME, next )
+ , m_name( perm.Name )
+ {}
+ virtual bool implies( Permission const & perm ) const override;
+ virtual OUString toString() const override;
+};
+
+}
+
+bool RuntimePermission::implies( Permission const & perm ) const
+{
+ // check type
+ if (RUNTIME != perm.m_type)
+ return false;
+ RuntimePermission const & demanded = static_cast< RuntimePermission const & >( perm );
+
+ // check name
+ return m_name == demanded.m_name;
+}
+
+OUString RuntimePermission::toString() const
+{
+ return "com.sun.star.security.RuntimePermission (name=\"" +
+ m_name + "\")";
+}
+
+
+bool AllPermission::implies( Permission const & ) const
+{
+ return true;
+}
+
+OUString AllPermission::toString() const
+{
+ return "com.sun.star.security.AllPermission";
+}
+
+
+PermissionCollection::PermissionCollection(
+ Sequence< Any > const & permissions, PermissionCollection const & addition )
+ : m_head( addition.m_head )
+{
+ Any const * perms = permissions.getConstArray();
+ for ( sal_Int32 nPos = permissions.getLength(); nPos--; )
+ {
+ Any const & perm = perms[ nPos ];
+ Type const & perm_type = perm.getValueType();
+
+ // supported permission types
+ if (perm_type.equals( cppu::UnoType<io::FilePermission>::get()))
+ {
+ m_head = new FilePermission(
+ *static_cast< io::FilePermission const * >( perm.pData ), m_head );
+ }
+ else if (perm_type.equals( cppu::UnoType<connection::SocketPermission>::get()))
+ {
+ m_head = new SocketPermission(
+ *static_cast< connection::SocketPermission const * >( perm.pData ), m_head );
+ }
+ else if (perm_type.equals( cppu::UnoType<security::RuntimePermission>::get()))
+ {
+ m_head = new RuntimePermission(
+ *static_cast< security::RuntimePermission const * >( perm.pData ), m_head );
+ }
+ else if (perm_type.equals( cppu::UnoType<security::AllPermission>::get()))
+ {
+ m_head = new AllPermission( m_head );
+ }
+ else
+ {
+ throw RuntimeException( "checking for unsupported permission type: " + perm_type.getTypeName() );
+ }
+ }
+}
+#ifdef __DIAGNOSE
+
+Sequence< OUString > PermissionCollection::toStrings() const
+{
+ vector< OUString > strings;
+ strings.reserve( 8 );
+ for ( Permission * perm = m_head.get(); perm; perm = perm->m_next.get() )
+ {
+ strings.push_back( perm->toString() );
+ }
+ return Sequence< OUString >( strings.data(), strings.size() );
+}
+#endif
+
+static bool implies(
+ ::rtl::Reference< Permission > const & head, Permission const & demanded )
+{
+ for ( Permission * perm = head.get(); perm; perm = perm->m_next.get() )
+ {
+ if (perm->implies( demanded ))
+ return true;
+ }
+ return false;
+}
+
+#ifdef __DIAGNOSE
+
+static void demanded_diag(
+ Permission const & perm )
+{
+ OUStringBuffer buf( 48 );
+ buf.append( "demanding " );
+ buf.append( perm.toString() );
+ buf.append( " => ok." );
+ OString str(
+ OUStringToOString( buf.makeStringAndClear(), RTL_TEXTENCODING_ASCII_US ) );
+ SAL_INFO("stoc",( "%s", str.getStr() );
+}
+#endif
+
+static void throwAccessControlException(
+ Permission const & perm, Any const & demanded_perm )
+{
+ throw security::AccessControlException(
+ "access denied: " + perm.toString(),
+ Reference< XInterface >(), demanded_perm );
+}
+
+void PermissionCollection::checkPermission( Any const & perm ) const
+{
+ Type const & demanded_type = perm.getValueType();
+
+ // supported permission types
+ // stack object of SimpleReferenceObject are ok, as long as they are not
+ // assigned to a ::rtl::Reference<> (=> delete this)
+ if (demanded_type.equals( cppu::UnoType<io::FilePermission>::get()))
+ {
+ FilePermission demanded(
+ *static_cast< io::FilePermission const * >( perm.pData ) );
+ if (implies( m_head, demanded ))
+ {
+#ifdef __DIAGNOSE
+ demanded_diag( demanded );
+#endif
+ return;
+ }
+ throwAccessControlException( demanded, perm );
+ }
+ else if (demanded_type.equals( cppu::UnoType<connection::SocketPermission>::get()))
+ {
+ SocketPermission demanded(
+ *static_cast< connection::SocketPermission const * >( perm.pData ) );
+ if (implies( m_head, demanded ))
+ {
+#ifdef __DIAGNOSE
+ demanded_diag( demanded );
+#endif
+ return;
+ }
+ throwAccessControlException( demanded, perm );
+ }
+ else if (demanded_type.equals( cppu::UnoType<security::RuntimePermission>::get()))
+ {
+ RuntimePermission demanded(
+ *static_cast< security::RuntimePermission const * >( perm.pData ) );
+ if (implies( m_head, demanded ))
+ {
+#ifdef __DIAGNOSE
+ demanded_diag( demanded );
+#endif
+ return;
+ }
+ throwAccessControlException( demanded, perm );
+ }
+ else if (demanded_type.equals( cppu::UnoType<security::AllPermission>::get()))
+ {
+ AllPermission demanded;
+ if (implies( m_head, demanded ))
+ {
+#ifdef __DIAGNOSE
+ demanded_diag( demanded );
+#endif
+ return;
+ }
+ throwAccessControlException( demanded, perm );
+ }
+ else
+ {
+ throw RuntimeException( "checking for unsupported permission type: " + demanded_type.getTypeName() );
+ }
+}
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/stoc/source/security/permissions.h b/stoc/source/security/permissions.h
new file mode 100644
index 000000000..eb0b30778
--- /dev/null
+++ b/stoc/source/security/permissions.h
@@ -0,0 +1,85 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+#ifndef INCLUDED_STOC_SOURCE_SECURITY_PERMISSIONS_H
+#define INCLUDED_STOC_SOURCE_SECURITY_PERMISSIONS_H
+
+#include <rtl/ref.hxx>
+#include <rtl/ustring.hxx>
+#include <salhelper/simplereferenceobject.hxx>
+
+namespace com::sun::star::uno { class Any; }
+namespace com::sun::star::uno { template <class E> class Sequence; }
+
+namespace stoc_sec
+{
+
+class Permission : public ::salhelper::SimpleReferenceObject
+{
+public:
+ ::rtl::Reference< Permission > m_next;
+ // mode
+ enum t_type { ALL, RUNTIME, SOCKET, FILE } m_type;
+
+ Permission(
+ t_type type,
+ ::rtl::Reference< Permission > const & next )
+ : m_next( next )
+ , m_type( type )
+ {}
+
+ virtual bool implies( Permission const & perm ) const = 0;
+ virtual OUString toString() const = 0;
+};
+
+class AllPermission : public Permission
+{
+public:
+ explicit AllPermission(
+ ::rtl::Reference< Permission > const & next = ::rtl::Reference< Permission >() )
+ : Permission( ALL, next )
+ {}
+
+ virtual bool implies( Permission const & ) const override;
+ virtual OUString toString() const override;
+};
+
+
+class PermissionCollection
+{
+ ::rtl::Reference< Permission > m_head;
+public:
+ PermissionCollection()
+ {}
+ explicit PermissionCollection( ::rtl::Reference< Permission > const & single )
+ : m_head( single )
+ {}
+ PermissionCollection(
+ css::uno::Sequence< css::uno::Any > const & permissions,
+ PermissionCollection const & addition = PermissionCollection() );
+#ifdef __DIAGNOSE
+ css::uno::Sequence< OUString > toStrings() const;
+#endif
+ void checkPermission( css::uno::Any const & perm ) const;
+};
+
+}
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */