diff options
Diffstat (limited to 'stoc/source/security')
-rw-r--r-- | stoc/source/security/access_controller.cxx | 863 | ||||
-rw-r--r-- | stoc/source/security/file_policy.cxx | 493 | ||||
-rw-r--r-- | stoc/source/security/lru_cache.h | 210 | ||||
-rw-r--r-- | stoc/source/security/permissions.cxx | 604 | ||||
-rw-r--r-- | stoc/source/security/permissions.h | 85 |
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: */ |