/* -*- 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 ::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=\""
        + m_host );
    if (m_resolvedHost)
    {
        buf.append( "[" + m_ip  + "]" );
    }
    // 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=\""
        + makeStrings( m_actions, s_actions )
        + "\")" );
    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|
    {
        // common case in API is a ':' (sal), so convert '|' to ':'
        m_url = m_url.replaceAt(9, 1, u":");
    }
#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 u"com.sun.star.security.AllPermission"_ustr;
}
 
 
PermissionCollection::PermissionCollection(
    Sequence< Any > const & permissions, PermissionCollection const & addition )
    : m_head( addition.m_head )
{
    for ( sal_Int32 nPos = permissions.getLength(); nPos--; )
    {
        Any const& perm = permissions[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
{
    std::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: */

V530 The return value of function 'appendAscii' is required to be utilized.

V530 The return value of function 'append' is required to be utilized.

V530 The return value of function 'append' is required to be utilized.

V530 The return value of function 'append' is required to be utilized.

V530 The return value of function 'append' is required to be utilized.

V530 The return value of function 'append' is required to be utilized.