/*************************************************************************
 *
 *  OpenOffice.org - a multi-platform office productivity suite
 *
 *  $RCSfile: file_policy.cxx,v $
 *
 *  $Revision: 1.7 $
 *
 *  last change: $Author: obo $ $Date: 2006-09-16 17:36:04 $
 *
 *  The Contents of this file are made available subject to
 *  the terms of GNU Lesser General Public License Version 2.1.
 *
 *
 *    GNU Lesser General Public License Version 2.1
 *    =============================================
 *    Copyright 2005 by Sun Microsystems, Inc.
 *    901 San Antonio Road, Palo Alto, CA 94303, USA
 *
 *    This library is free software; you can redistribute it and/or
 *    modify it under the terms of the GNU Lesser General Public
 *    License version 2.1, as published by the Free Software Foundation.
 *
 *    This library is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *    Lesser General Public License for more details.
 *
 *    You should have received a copy of the GNU Lesser General Public
 *    License along with this library; if not, write to the Free Software
 *    Foundation, Inc., 59 Temple Place, Suite 330, Boston,
 *    MA  02111-1307  USA
 *
 ************************************************************************/

// MARKER(update_precomp.py): autogen include statement, do not remove
#include "precompiled_stoc.hxx"

#include <hash_map>

#include <osl/diagnose.h>
#include <osl/file.h>
#include <rtl/byteseq.hxx>
#include <rtl/string.hxx>
#include <rtl/ustrbuf.hxx>

#include <cppuhelper/access_control.hxx>
#include <cppuhelper/compbase2.hxx>
#include <cppuhelper/implementationentry.hxx>

#include <com/sun/star/lang/XServiceInfo.hpp>
#include <com/sun/star/security/XAccessController.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>

#define OUSTR(x) ::rtl::OUString( RTL_CONSTASCII_USTRINGPARAM(x) )
#define SERVICE_NAME "com.sun.star.security.Policy"
#define IMPL_NAME "com.sun.star.security.comp.stoc.FilePolicy"


using namespace ::osl;
using namespace ::rtl;
using namespace ::cppu;
using namespace ::com::sun::star;
using namespace ::com::sun::star::uno;

namespace stoc_sec
{

// static stuff initialized when loading lib
static OUString s_implName = OUSTR(IMPL_NAME);
static OUString s_serviceName = OUSTR(SERVICE_NAME);

static Sequence< OUString > s_serviceNames = Sequence< OUString >( &s_serviceName, 1 );
extern ::rtl_StandardModuleCount s_moduleCount;

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

//--------------------------------------------------------------------------------------------------
static inline void dispose( Reference< XInterface > const & x )
    SAL_THROW( (RuntimeException) )
{
    Reference< lang::XComponent > xComp( x, UNO_QUERY );
    if (xComp.is())
    {
        xComp->dispose();
    }
}

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

struct MutexHolder
{
    Mutex m_mutex;
};
typedef WeakComponentImplHelper2< security::XPolicy, lang::XServiceInfo > t_helper;

//==================================================================================================
class FilePolicy
    : public MutexHolder
    , public t_helper
{
    Reference< XComponentContext > m_xComponentContext;
    AccessControl m_ac;

    Sequence< Any > m_defaultPermissions;
    typedef std::hash_map< OUString, Sequence< Any >, OUStringHash > t_permissions;
    t_permissions m_userPermissions;
    bool m_init;

protected:
    virtual void SAL_CALL disposing();

public:
    FilePolicy( Reference< XComponentContext > const & xComponentContext )
        SAL_THROW( () );
    virtual ~FilePolicy()
        SAL_THROW( () );

    // XPolicy impl
    virtual Sequence< Any > SAL_CALL getPermissions(
        OUString const & userId )
        throw (RuntimeException);
    virtual Sequence< Any > SAL_CALL getDefaultPermissions()
        throw (RuntimeException);
    virtual void SAL_CALL refresh()
        throw (RuntimeException);

    // XServiceInfo impl
    virtual OUString SAL_CALL getImplementationName()
        throw (RuntimeException);
    virtual sal_Bool SAL_CALL supportsService( OUString const & serviceName )
        throw (RuntimeException);
    virtual Sequence< OUString > SAL_CALL getSupportedServiceNames()
        throw (RuntimeException);
};
//__________________________________________________________________________________________________
FilePolicy::FilePolicy( Reference< XComponentContext > const & xComponentContext )
    SAL_THROW( () )
    : t_helper( m_mutex )
    , m_xComponentContext( xComponentContext )
    , m_ac( xComponentContext )
    , m_init( false )
{
    s_moduleCount.modCnt.acquire( &s_moduleCount.modCnt );
}
//__________________________________________________________________________________________________
FilePolicy::~FilePolicy()
    SAL_THROW( () )
{
    s_moduleCount.modCnt.release( &s_moduleCount.modCnt );
}
//__________________________________________________________________________________________________
void FilePolicy::disposing()
{
    m_userPermissions.clear();
    m_defaultPermissions = Sequence< Any >();
    m_xComponentContext.clear();
}

//__________________________________________________________________________________________________
Sequence< Any > FilePolicy::getPermissions(
    OUString const & userId )
    throw (RuntimeException)
{
    if (! m_init)
    {
        refresh();
        m_init = true;
    }

    MutexGuard guard( m_mutex );
    t_permissions::iterator iFind( m_userPermissions.find( userId ) );
    if (m_userPermissions.end() == iFind)
    {
        return Sequence< Any >();
    }
    else
    {
        return iFind->second;
    }
}
//__________________________________________________________________________________________________
Sequence< Any > FilePolicy::getDefaultPermissions()
    throw (RuntimeException)
{
    if (! m_init)
    {
        refresh();
        m_init = true;
    }

    MutexGuard guard( m_mutex );
    return m_defaultPermissions;
}

//==================================================================================================
class PolicyReader
{
    OUString m_fileName;
    oslFileHandle m_file;

    sal_Int32 m_linepos;
    ByteSequence m_line;
    sal_Int32 m_pos;
    sal_Unicode m_back;

    sal_Unicode get()
        SAL_THROW( (RuntimeException) );
    inline void back( sal_Unicode c ) SAL_THROW( () )
        { m_back = c; }

    inline bool isWhiteSpace( sal_Unicode c ) SAL_THROW( () )
        { return (' ' == c || '\t' == c || '\n' == c || '\r' == c); }
    void skipWhiteSpace()
        SAL_THROW( (RuntimeException) );

    inline bool isCharToken( sal_Unicode c ) SAL_THROW( () )
        { return (';' == c || ',' == c || '{' == c || '}' == c); }

public:
    PolicyReader( OUString const & file, AccessControl & ac )
        SAL_THROW( (RuntimeException) );
    ~PolicyReader()
        SAL_THROW( () );

    void error( OUString const & msg )
        SAL_THROW( (RuntimeException) );

    OUString getToken()
        SAL_THROW( (RuntimeException) );
    OUString assureToken()
        SAL_THROW( (RuntimeException) );
    OUString getQuotedToken()
        SAL_THROW( (RuntimeException) );
    OUString assureQuotedToken()
        SAL_THROW( (RuntimeException) );
    void assureToken( sal_Unicode token )
        SAL_THROW( (RuntimeException) );
};
//__________________________________________________________________________________________________
void PolicyReader::assureToken( sal_Unicode token )
    SAL_THROW( (RuntimeException) )
{
    skipWhiteSpace();
    sal_Unicode c = get();
    if (c == token)
        return;
    OUStringBuffer buf( 16 );
    buf.appendAscii( RTL_CONSTASCII_STRINGPARAM("expected >") );
    buf.append( c );
    buf.appendAscii( RTL_CONSTASCII_STRINGPARAM("<!") );
    error( buf.makeStringAndClear() );
}
//__________________________________________________________________________________________________
OUString PolicyReader::assureQuotedToken()
    SAL_THROW( (RuntimeException) )
{
    OUString token( getQuotedToken() );
    if (! token.getLength())
        error( OUSTR("unexpected end of file!") );
    return token;
}
//__________________________________________________________________________________________________
OUString PolicyReader::getQuotedToken()
    SAL_THROW( (RuntimeException) )
{
    skipWhiteSpace();
    OUStringBuffer buf( 32 );
    sal_Unicode c = get();
    if ('\"' != c)
        error( OUSTR("expected quoting >\"< character!") );
    c = get();
    while ('\0' != c && '\"' != c)
    {
        buf.append( c );
        c = get();
    }
    return buf.makeStringAndClear();
}
//__________________________________________________________________________________________________
OUString PolicyReader::assureToken()
    SAL_THROW( (RuntimeException) )
{
    OUString token( getToken() );
    if (! token.getLength())
        error( OUSTR("unexpected end of file!") );
    return token;
}
//__________________________________________________________________________________________________
OUString PolicyReader::getToken()
    SAL_THROW( (RuntimeException) )
{
    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_THROW( (RuntimeException) )
{
    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( OUSTR("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()
    SAL_THROW( (RuntimeException) )
{
    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( OUSTR("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( OUSTR("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( OUString const & msg )
    SAL_THROW( (RuntimeException) )
{
    OUStringBuffer buf( 32 );
    buf.appendAscii( RTL_CONSTASCII_STRINGPARAM("error processing file \"") );
    buf.append( m_fileName );
    buf.appendAscii( RTL_CONSTASCII_STRINGPARAM("\" [line ") );
    buf.append( m_linepos );
    buf.appendAscii( RTL_CONSTASCII_STRINGPARAM(", column ") );
    buf.append( m_pos );
    buf.appendAscii( RTL_CONSTASCII_STRINGPARAM("] ") );
    buf.append( msg );
    throw RuntimeException( buf.makeStringAndClear(), Reference< XInterface >() );
}
//__________________________________________________________________________________________________
PolicyReader::PolicyReader( OUString const & fileName, AccessControl & ac )
    SAL_THROW( (RuntimeException) )
    : m_fileName( fileName )
    , m_linepos( 0 )
    , m_pos( 1 ) // force readline
    , m_back( '\0' )
{
    ac.checkFilePermission( m_fileName, OUSTR("read") );
    if (osl_File_E_None != ::osl_openFile( m_fileName.pData, &m_file, osl_File_OpenFlag_Read ))
    {
        OUStringBuffer buf( 32 );
        buf.appendAscii( RTL_CONSTASCII_STRINGPARAM("cannot open file \"") );
        buf.append( m_fileName );
        buf.appendAscii( RTL_CONSTASCII_STRINGPARAM("\"!") );
        throw RuntimeException( buf.makeStringAndClear(), Reference< XInterface >() );
    }
}
//__________________________________________________________________________________________________
PolicyReader::~PolicyReader()
    SAL_THROW( () )
{
    if ( ::osl_closeFile( m_file ) != osl_File_E_None ) {
        OSL_ASSERT( false );
    }
}

static OUString s_grant = OUSTR("grant");
static OUString s_user = OUSTR("user");
static OUString s_permission = OUSTR("permission");
static OUString s_openBrace = OUSTR("{");
static OUString s_closingBrace = OUSTR("}");
static OUString s_semi = OUSTR(";");

static OUString s_filePermission = OUSTR("com.sun.star.io.FilePermission");
static OUString s_socketPermission = OUSTR("com.sun.star.connection.SocketPermission");
static OUString s_runtimePermission = OUSTR("com.sun.star.security.RuntimePermission");
static OUString s_allPermission = OUSTR("com.sun.star.security.AllPermission");

//__________________________________________________________________________________________________
void FilePolicy::refresh()
    throw (RuntimeException)
{
    // read out file
    OUString fileName;
    m_xComponentContext->getValueByName(
        OUSTR("/implementations/" IMPL_NAME "/file-name") ) >>= fileName;
    if (! fileName.getLength())
    {
        throw RuntimeException(
            OUSTR("name of policy file unknown!"),
            (OWeakObject *)this );
    }

    PolicyReader reader( fileName, m_ac );

    // fill these two
    Sequence< Any > defaultPermissions;
    t_permissions userPermissions;

    OUString token( reader.getToken() );
    while (token.getLength())
    {
        if (! token.equals( s_grant ))
            reader.error( OUSTR("expected >grant< token!") );
        OUString userId;
        token = reader.assureToken();
        if (token.equals( s_user )) // next token is user-id
        {
            userId = reader.assureQuotedToken();
            token = reader.assureToken();
        }
        if (! token.equals( s_openBrace ))
            reader.error( OUSTR("expected opening brace >{<!") );
        token = reader.assureToken();
        // permissions list
        while (! token.equals( s_closingBrace ))
        {
            if (! token.equals( s_permission ))
                reader.error( OUSTR("expected >permission< or closing brace >}<!") );

            token = reader.assureToken(); // permission type
            Any perm;
            if (token.equals( s_filePermission )) // FilePermission
            {
                OUString url( reader.assureQuotedToken() );
                reader.assureToken( ',' );
                OUString actions( reader.assureQuotedToken() );
                perm <<= io::FilePermission( url, actions );
            }
            else if (token.equals( s_socketPermission )) // SocketPermission
            {
                OUString host( reader.assureQuotedToken() );
                reader.assureToken( ',' );
                OUString actions( reader.assureQuotedToken() );
                perm <<= connection::SocketPermission( host, actions );
            }
            else if (token.equals( s_runtimePermission )) // RuntimePermission
            {
                OUString name( reader.assureQuotedToken() );
                perm <<= security::RuntimePermission( name );
            }
            else if (token.equals( s_allPermission )) // AllPermission
            {
                perm <<= security::AllPermission();
            }
            else
            {
                reader.error( OUSTR("expected permission type!") );
            }

            reader.assureToken( ';' );

            // insert
            if (userId.getLength())
            {
                Sequence< Any > perms( userPermissions[ userId ] );
                sal_Int32 len = perms.getLength();
                perms.realloc( len +1 );
                perms[ len ] = perm;
                userPermissions[ userId ] = perms;
            }
            else
            {
                sal_Int32 len = defaultPermissions.getLength();
                defaultPermissions.realloc( len +1 );
                defaultPermissions[ len ] = perm;
            }

            token = reader.assureToken(); // next permissions token
        }

        reader.assureToken( ';' ); // semi
        token = reader.getToken(); // next grant token
    }

    // assign new ones
    MutexGuard guard( m_mutex );
    m_defaultPermissions = defaultPermissions;
    m_userPermissions = userPermissions;
}

//__________________________________________________________________________________________________
OUString FilePolicy::getImplementationName()
    throw (RuntimeException)
{
    return s_implName;
}
//__________________________________________________________________________________________________
sal_Bool FilePolicy::supportsService( OUString const & serviceName )
    throw (RuntimeException)
{
    OUString const * pNames = s_serviceNames.getConstArray();
    for ( sal_Int32 nPos = s_serviceNames.getLength(); nPos--; )
    {
        if (serviceName.equals( pNames[ nPos ] ))
        {
            return sal_True;
        }
    }
    return sal_False;
}
//__________________________________________________________________________________________________
Sequence< OUString > FilePolicy::getSupportedServiceNames()
    throw (RuntimeException)
{
    return s_serviceNames;
}

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

//--------------------------------------------------------------------------------------------------
Reference< XInterface > SAL_CALL filepolicy_create(
    Reference< XComponentContext > const & xComponentContext )
    SAL_THROW( (Exception) )
{
    return (OWeakObject *)new FilePolicy( xComponentContext );
}
//--------------------------------------------------------------------------------------------------
Sequence< OUString > filepolicy_getSupportedServiceNames() SAL_THROW( () )
{
    return s_serviceNames;
}
//--------------------------------------------------------------------------------------------------
OUString filepolicy_getImplementationName() SAL_THROW( () )
{
    return s_implName;
}

}
