# POPFILE LOADABLE MODULE
package UI::XMLRPC;

#----------------------------------------------------------------------------
#
# This package contains the XML-RPC interface for POPFile, all the methods
# in Classifier::Bayes can be accessed through the XMLRPC interface and
# a typical method would be accessed as follows
#
#     Classifier/Bayes.get_buckets
#
# Copyright (c) 2001-2004 John Graham-Cumming
#
#   This file is part of POPFile
#
#   POPFile is free software; you can redistribute it and/or modify
#   it under the terms of the GNU General Public License as published by
#   the Free Software Foundation; either version 2 of the License, or
#   (at your option) any later version.
#
#   POPFile 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 General Public License for more details.
#
#   You should have received a copy of the GNU General Public License
#   along with POPFile; if not, write to the Free Software
#   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
#   Modified by     Sam Schinke (sschinke@users.sourceforge.net)
#
#----------------------------------------------------------------------------

use POPFile::Module;
@ISA = ("POPFile::Module");

use POPFile::API;

use strict;
use warnings;
use locale;

use IO::Socket;
use IO::Select;

my $eol = "\015\012";

#----------------------------------------------------------------------------
# new
#
#   Class new() function
#----------------------------------------------------------------------------
sub new
{
    my $type = shift;
    my $self = POPFile::Module->new();

    bless $self, $type;;

    $self->name( 'xmlrpc' );

    return $self;
}

# ----------------------------------------------------------------------------
#
# initialize
#
# Called to initialize the interface
#
# ----------------------------------------------------------------------------
sub initialize
{
    my ( $self ) = @_;

    # By default we are disabled

    $self->config_( 'enabled', 0 );

    # XML-RPC is available on port 8081 initially

    $self->config_( 'port', 8081 );

    # Only accept connections from the local machine

    $self->config_( 'local', 1 );

    $self->{api__} = new POPFile::API;

    return 1;
}

# ----------------------------------------------------------------------------
#
# start
#
# Called to start the XMLRPC interface running
#
# ----------------------------------------------------------------------------
sub start
{
    my ( $self ) = @_;

    if ( $self->config_( 'enabled' ) == 0 ) {
        return 2;
    }

    require XMLRPC::Transport::HTTP;

    # Tell the user interface module that we having a configuration
    # item that needs a UI component

    $self->register_configuration_item_( 'configuration',
                                         'xmlrpc_port',
                                         'xmlrpc-port.thtml',
                                         $self );

    $self->register_configuration_item_( 'security',
                                         'xmlrpc_local',
                                         'xmlrpc-local.thtml',
                                         $self );

    # We use a single XMLRPC::Lite object to handle requests for access to the
    # Classifier::Bayes object

    $self->{server__} = XMLRPC::Transport::HTTP::Daemon->new(   # PROFILE BLOCK START
                                     Proto     => 'tcp',
                                     $self->config_( 'local' )  == 1 ? (LocalAddr => 'localhost') : (),
                                     LocalPort => $self->config_( 'port' ),
                                     Listen    => SOMAXCONN,
                                     Reuse     => 1 );          # PROFILE BLOCK STOP

    if ( !defined( $self->{server__} ) ) {
        my $port = $self->config_( 'port' );
        my $name = $self->name();

        print <<EOM;

\nCouldn't start the $name HTTP interface because POPFile could not bind to the
HTTP port $port. This could be because there is another service
using that port or because you do not have the right privileges on
your system (On Unix systems this can happen if you are not root
and the port you specified is less than 1024).

EOM
                    #' # fix some syntax highlighting editors
        return 0;
    }


    # All requests will get dispatched to the main Classifier::Bayes object, for example
    # the get_bucket_color interface is accessed with the method name.  The actual
    # dispatch is via the POPFile::API object which we create in initialize above.
    #
    #     POPFile/API.get_bucket_color

    $self->{api__}->{c} = $self->{classifier__};
    $self->{server__}->dispatch_to( $self->{api__} );

    # DANGER WILL ROBINSON!  In order to make a polling XML-RPC server I am using
    # the XMLRPC::Transport::HTTP::Daemon class which uses blocking I/O.  This would
    # be all very well but it seems to be totally ignorning signals on Windows and so
    # POPFile is unstoppable when the handle() method is called.  Forking with this
    # blocking doesn't help much because then we get an unstoppable child.
    #
    # So the solution relies on knowing the internals of XMLRPC::Transport::HTTP::Daemon
    # which is actually a SOAP::Transport::HTTP::Daemon which has a HTTP::Daemon (stored
    # in a private variable called _daemon.  HTTP::Daemon is an IO::Socket::INET which means
    # we can create a selector on it, so here we access a PRIVATE variable on the XMLRPC
    # object.  This is very bad behaviour, but it works until someone changes XMLRPC.

    $self->{selector__} = new IO::Select( $self->{server__}->{_daemon} );

    return 1;
}

# ----------------------------------------------------------------------------
#
# service
#
# Called to handle interface requests
#
# ----------------------------------------------------------------------------
sub service
{
    my ( $self ) = @_;

    # See if there's a connection pending on the XMLRPC socket and handle
    # single request

    my ( $ready ) = $self->{selector__}->can_read(0);

    if ( defined( $ready ) ) {
        if ( my $client = $self->{server__}->accept() ) {

            # Check that this is a connection from the local machine, if it's not then we drop it immediately
            # without any further processing.  We don't want to allow remote users to admin POPFile

            my ( $remote_port, $remote_host ) = sockaddr_in( $client->peername() );

            if ( ( $self->config_( 'local' ) == 0 ) ||              # PROFILE BLOCK START
                 ( $remote_host eq inet_aton( "127.0.0.1" ) ) ) {   # PROFILE BLOCK STOP
                my $request = $client->get_request();

                # Note that handle() relies on the $request being perfectly valid, so here we
                # check that it is, if it is not then we don't want to call handle and we'll
                # return out own error

                if ( defined( $request ) ) {
                    $self->{server__}->request( $request );

                    # Note the direct call to SOAP::Transport::HTTP::Server::handle() here, this is
                    # because we have taken the code from XMLRPC::Transport::HTTP::Server::handle()
                    # and reproduced a modification of it here, accepting a single request and handling
                    # it.  This call to the parent of XMLRPC::Transport::HTTP::Server will actually
                    # deal with the request

                    $self->{server__}->SOAP::Transport::HTTP::Server::handle();
                    $client->send_response( $self->{server__}->response );
		}
                $client->close();
            }
        }
    }

    return 1;
}

# ----------------------------------------------------------------------------
#
# configure_item
#
#    $name            Name of this item
#    $templ           The loaded template that was passed as a parameter
#                     when registering
#    $language        Current language
#
# ----------------------------------------------------------------------------

sub configure_item
{
    my ( $self, $name, $templ, $language ) = @_;

    if ( $name eq 'xmlrpc_port' ) {
        $templ->param ( 'XMLRPC_Port' => $self->config_( 'port' ) );
    }

    if ( $name eq 'xmlrpc_local' ) {
        
        if ( $self->config_( 'local' ) == 1 ) {
            $templ->param( 'XMLRPC_local_on' => 1 );
        }
        else {
            $templ->param( 'XMLRPC_local_on' => 0 );
        }
    }
}

# ----------------------------------------------------------------------------
#
# validate_item
#
#    $name            The name of the item being configured, was passed in by the call
#                     to register_configuration_item
#    $templ           The loaded template
#    $language        The language currently in use
#    $form            Hash containing all form items
#
# ----------------------------------------------------------------------------

sub validate_item
{
    my ( $self, $name, $templ, $language, $form ) = @_;

    # Just check to see if the XML rpc port was change and check its value

    if ( $name eq 'xmlrpc_port' ) {
        if ( defined($$form{xmlrpc_port}) ) {
            if ( ( $$form{xmlrpc_port} >= 1 ) && ( $$form{xmlrpc_port} < 65536 ) ) {
                $self->config_( 'port', $$form{xmlrpc_port} );
                $templ->param( 'XMLRPC_port_if_error' => 0 );
                $templ->param( 'XMLRPC_port_updated' => sprintf( $$language{Configuration_XMLRPCUpdate}, $self->config_( 'port' ) ) );
            } 
            else {
                $templ->param( 'XMLRPC_port_if_error' => 1 );
            }
        }
    }

    if ( $name eq 'xmlrpc_local' ) {
        $self->config_( 'local', $$form{xmlrpc_local}-1 ) if ( defined($$form{xmlrpc_local}) );
    }

    return '';
}

# GETTERS/SETTERS

sub classifier
{
    my ( $self, $value ) = @_;

    if ( defined( $value ) ) {
        $self->{classifier__} = $value;
    }

    return $self->{classifier__};
}

sub history
{
    my ( $self, $value ) = @_;

    if ( defined( $value ) ) {
        $self->{history__} = $value;
    }

    return $self->{history__};
}

1;

