/* -*- 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 <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 "permissions.h"

#define OUSTR(x) ::rtl::OUString( RTL_CONSTASCII_USTRINGPARAM(x) )


using namespace ::std;
using namespace ::osl;
using namespace ::com::sun::star;
using namespace ::com::sun::star::uno;
using ::rtl::OUString;
using ::rtl::OUStringBuffer;

namespace stoc_sec
{

//--------------------------------------------------------------------------------------------------
static inline sal_Int32 makeMask(
    OUString const & items, char const * const * strings ) SAL_THROW(())
{
    sal_Int32 mask = 0;

    sal_Int32 n = 0;
    do
    {
        OUString item( items.getToken( 0, ',', n ).trim() );
        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 ])
        {
            OUStringBuffer buf( 48 );
            buf.appendAscii( RTL_CONSTASCII_STRINGPARAM("### ignoring unknown socket action: ") );
            buf.append( item );
            ::rtl::OString str( ::rtl::OUStringToOString(
                buf.makeStringAndClear(), RTL_TEXTENCODING_ASCII_US ) );
            OSL_TRACE( "%s", str.getStr() );
        }
#endif
    }
    while (n >= 0); // all items
    return mask;
}
//--------------------------------------------------------------------------------------------------
static inline OUString makeStrings(
    sal_Int32 mask, char const * const * strings ) SAL_THROW(())
{
    OUStringBuffer buf( 48 );
    while (mask)
    {
        if (0x80000000 & mask)
        {
            buf.appendAscii( *strings );
            if (mask << 1) // more items following
                buf.append( (sal_Unicode)',' );
        }
        mask = (mask << 1);
        ++strings;
    }
    return buf.makeStringAndClear();
}

//##################################################################################################

//==================================================================================================
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 SAL_THROW(());

public:
    SocketPermission(
        connection::SocketPermission const & perm,
        ::rtl::Reference< Permission > const & next = ::rtl::Reference< Permission >() )
        SAL_THROW(());
    virtual bool implies( Permission const & perm ) const SAL_THROW(());
    virtual OUString toString() const SAL_THROW(());
};
//__________________________________________________________________________________________________
char const * SocketPermission::s_actions [] = { "accept", "connect", "listen", "resolve", 0 };
//__________________________________________________________________________________________________
SocketPermission::SocketPermission(
    connection::SocketPermission const & perm,
    ::rtl::Reference< Permission > const & next )
    SAL_THROW(())
    : 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] given
    {
        sal_Int32 minus = m_host.indexOf( '-', colon +1 );
        if (minus < 0)
        {
            m_lowerPort = m_upperPort = m_host.copy( colon +1 ).toInt32();
        }
        else if (minus == (colon +1)) // -N
        {
            m_upperPort = m_host.copy( minus +1 ).toInt32();
        }
        else if (minus == (m_host.getLength() -1)) // N-
        {
            m_lowerPort = m_host.copy( colon +1, m_host.getLength() -1 -colon -1 ).toInt32();
        }
        else // A-B
        {
            m_lowerPort = m_host.copy( colon +1, minus - colon -1 ).toInt32();
            m_upperPort = m_host.copy( minus +1, m_host.getLength() -minus -1 ).toInt32();
        }
        m_host = m_host.copy( 0, colon );
    }
}
//__________________________________________________________________________________________________
inline bool SocketPermission::resolveHost() const SAL_THROW(())
{
    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 SAL_THROW(())
{
    // 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 (sal_False != m_ip.equals( demanded.m_ip ));
}
//__________________________________________________________________________________________________
OUString SocketPermission::toString() const SAL_THROW(())
{
    OUStringBuffer buf( 48 );
    // host
    buf.appendAscii(
        RTL_CONSTASCII_STRINGPARAM("com.sun.star.connection.SocketPermission (host=\"") );
    buf.append( m_host );
    if (m_resolvedHost)
    {
        buf.append( (sal_Unicode)'[' );
        buf.append( m_ip );
        buf.append( (sal_Unicode)']' );
    }
    // port
    if (0 != m_lowerPort || 65535 != m_upperPort)
    {
        buf.append( (sal_Unicode)':' );
        if (m_lowerPort > 0)
            buf.append( m_lowerPort );
        if (m_upperPort > m_lowerPort)
        {
            buf.append( (sal_Unicode)'-' );
            if (m_upperPort < 65535)
                buf.append( m_upperPort );
        }
    }
    // actions
    buf.appendAscii( RTL_CONSTASCII_STRINGPARAM("\", actions=\"") );
    buf.append( makeStrings( m_actions, s_actions ) );
    buf.appendAscii( RTL_CONSTASCII_STRINGPARAM("\")") );
    return buf.makeStringAndClear();
}

//##################################################################################################

//==================================================================================================
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 >() )
        SAL_THROW(());
    virtual bool implies( Permission const & perm ) const SAL_THROW(());
    virtual OUString toString() const SAL_THROW(());
};
//__________________________________________________________________________________________________
char const * FilePermission::s_actions [] = { "read", "write", "execute", "delete", 0 };
//--------------------------------------------------------------------------------------------------
static OUString const & getWorkingDir() SAL_THROW(())
{
    static OUString * s_workingDir = 0;
    if (! s_workingDir)
    {
        OUString workingDir;
        ::osl_getProcessWorkingDir( &workingDir.pData );

        MutexGuard guard( Mutex::getGlobalMutex() );
        if (! s_workingDir)
        {
            static OUString s_dir( workingDir );
            s_workingDir = &s_dir;
        }
    }
    return *s_workingDir;
}
//__________________________________________________________________________________________________
FilePermission::FilePermission(
    io::FilePermission const & perm,
    ::rtl::Reference< Permission > const & next )
    SAL_THROW(())
    : Permission( FILE, next )
    , m_actions( makeMask( perm.Actions, s_actions ) )
    , m_url( perm.URL )
    , m_allFiles( sal_False != perm.URL.equalsAsciiL(RTL_CONSTASCII_STRINGPARAM("<<ALL FILES>>")) )
{
    if (! m_allFiles)
    {
        if ( m_url == "*" )
        {
            OUStringBuffer buf( 64 );
            buf.append( getWorkingDir() );
            buf.appendAscii( RTL_CONSTASCII_STRINGPARAM("/*") );
            m_url = buf.makeStringAndClear();
        }
        else if ( m_url == "-" )
        {
            OUStringBuffer buf( 64 );
            buf.append( getWorkingDir() );
            buf.appendAscii( RTL_CONSTASCII_STRINGPARAM("/-") );
            m_url = buf.makeStringAndClear();
        }
        else if (0 != m_url.compareToAscii( RTL_CONSTASCII_STRINGPARAM("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 SAL_W32
        // correct win drive letters
        if (9 < m_url.getLength() && '|' == m_url[ 9 ]) // file:///X|
        {
            static OUString s_colon = OUSTR(":");
            // 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 SAL_THROW(())
{
    // 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 SAL_W32
    if (m_url.equalsIgnoreAsciiCase( demanded.m_url ))
        return true;
#else
    if (m_url.equals( 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 (1 < m_url.getLength() &&
        0 == ::rtl_ustr_ascii_compare_WithLength( m_url.getStr() + m_url.getLength() - 2, 2, "/-" ))
    {
        // demanded url must start with granted path (including path trailing path sep)
        sal_Int32 len = m_url.getLength() -1;
#ifdef SAL_W32
        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 (1 < m_url.getLength() &&
        0 == ::rtl_ustr_ascii_compare_WithLength( m_url.getStr() + m_url.getLength() - 2, 2, "/*" ))
    {
        // demanded url must start with granted path (including path trailing path sep)
        sal_Int32 len = m_url.getLength() -1;
#ifdef SAL_W32
        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 SAL_THROW(())
{
    OUStringBuffer buf( 48 );
    // url
    buf.appendAscii( RTL_CONSTASCII_STRINGPARAM("com.sun.star.io.FilePermission (url=\"") );
    buf.append( m_url );
    // actions
    buf.appendAscii( RTL_CONSTASCII_STRINGPARAM("\", actions=\"") );
    buf.append( makeStrings( m_actions, s_actions ) );
    buf.appendAscii( RTL_CONSTASCII_STRINGPARAM("\")") );
    return buf.makeStringAndClear();
}

//##################################################################################################

//==================================================================================================
class RuntimePermission : public Permission
{
    OUString m_name;

public:
    inline RuntimePermission(
        security::RuntimePermission const & perm,
        ::rtl::Reference< Permission > const & next = ::rtl::Reference< Permission >() )
        SAL_THROW(())
        : Permission( RUNTIME, next )
        , m_name( perm.Name )
        {}
    virtual bool implies( Permission const & perm ) const SAL_THROW(());
    virtual OUString toString() const SAL_THROW(());
};
//__________________________________________________________________________________________________
bool RuntimePermission::implies( Permission const & perm ) const SAL_THROW(())
{
    // check type
    if (RUNTIME != perm.m_type)
        return false;
    RuntimePermission const & demanded = static_cast< RuntimePermission const & >( perm );

    // check name
    return (sal_False != m_name.equals( demanded.m_name ));
}
//__________________________________________________________________________________________________
OUString RuntimePermission::toString() const SAL_THROW(())
{
    OUStringBuffer buf( 48 );
    buf.appendAscii(
        RTL_CONSTASCII_STRINGPARAM("com.sun.star.security.RuntimePermission (name=\"") );
    buf.append( m_name );
    buf.appendAscii( RTL_CONSTASCII_STRINGPARAM("\")") );
    return buf.makeStringAndClear();
}

//##################################################################################################

//__________________________________________________________________________________________________
bool AllPermission::implies( Permission const & ) const SAL_THROW(())
{
    return true;
}
//__________________________________________________________________________________________________
OUString AllPermission::toString() const SAL_THROW(())
{
    return OUSTR("com.sun.star.security.AllPermission");
}

//##################################################################################################

//__________________________________________________________________________________________________
PermissionCollection::PermissionCollection(
    Sequence< Any > const & permissions, PermissionCollection const & addition )
    SAL_THROW( (RuntimeException) )
    : 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( ::getCppuType( (io::FilePermission const *)0 ) ))
        {
            m_head = new FilePermission(
                *reinterpret_cast< io::FilePermission const * >( perm.pData ), m_head );
        }
        else if (perm_type.equals( ::getCppuType( (connection::SocketPermission const *)0 ) ))
        {
            m_head = new SocketPermission(
                *reinterpret_cast< connection::SocketPermission const * >( perm.pData ), m_head );
        }
        else if (perm_type.equals( ::getCppuType( (security::RuntimePermission const *)0 ) ))
        {
            m_head = new RuntimePermission(
                *reinterpret_cast< security::RuntimePermission const * >( perm.pData ), m_head );
        }
        else if (perm_type.equals( ::getCppuType( (security::AllPermission const *)0 ) ))
        {
            m_head = new AllPermission( m_head );
        }
        else
        {
            OUStringBuffer buf( 48 );
            buf.appendAscii( RTL_CONSTASCII_STRINGPARAM(
                "checking for unsupported permission type: ") );
            buf.append( perm_type.getTypeName() );
            throw RuntimeException(
                buf.makeStringAndClear(), Reference< XInterface >() );
        }
    }
}
#ifdef __DIAGNOSE
//__________________________________________________________________________________________________
Sequence< OUString > PermissionCollection::toStrings() const SAL_THROW(())
{
    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.empty() ? 0 : &strings[ 0 ], strings.size() );
}
#endif
//__________________________________________________________________________________________________
inline static bool __implies(
    ::rtl::Reference< Permission > const & head, Permission const & demanded ) SAL_THROW(())
{
    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 )
    SAL_THROW(())
{
    OUStringBuffer buf( 48 );
    buf.appendAscii( RTL_CONSTASCII_STRINGPARAM("demanding ") );
    buf.append( perm.toString() );
    buf.appendAscii( RTL_CONSTASCII_STRINGPARAM(" => ok.") );
    ::rtl::OString str(
        ::rtl::OUStringToOString( buf.makeStringAndClear(), RTL_TEXTENCODING_ASCII_US ) );
    OSL_TRACE( "%s", str.getStr() );
}
#endif
//--------------------------------------------------------------------------------------------------
static void throwAccessControlException(
    Permission const & perm, Any const & demanded_perm )
    SAL_THROW( (security::AccessControlException) )
{
    OUStringBuffer buf( 48 );
    buf.appendAscii( RTL_CONSTASCII_STRINGPARAM("access denied: ") );
    buf.append( perm.toString() );
    throw security::AccessControlException(
        buf.makeStringAndClear(), Reference< XInterface >(), demanded_perm );
}
//==================================================================================================
void PermissionCollection::checkPermission( Any const & perm ) const
    SAL_THROW( (RuntimeException) )
{
    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( ::getCppuType( (io::FilePermission const *)0 ) ))
    {
        FilePermission demanded(
            *reinterpret_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( ::getCppuType( (connection::SocketPermission const *)0 ) ))
    {
        SocketPermission demanded(
            *reinterpret_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( ::getCppuType( (security::RuntimePermission const *)0 ) ))
    {
        RuntimePermission demanded(
            *reinterpret_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( ::getCppuType( (security::AllPermission const *)0 ) ))
    {
        AllPermission demanded;
        if (__implies( m_head, demanded ))
        {
#ifdef __DIAGNOSE
            demanded_diag( demanded );
#endif
            return;
        }
        throwAccessControlException( demanded, perm );
    }
    else
    {
        OUStringBuffer buf( 48 );
        buf.appendAscii( RTL_CONSTASCII_STRINGPARAM("checking for unsupported permission type: ") );
        buf.append( demanded_type.getTypeName() );
        throw RuntimeException(
            buf.makeStringAndClear(), Reference< XInterface >() );
    }
}

}

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
