CipUX-RPC-3.4.0.9000755001750001750 011432067074 14100 5ustar00ckuelkerckuelker000000000000CipUX-RPC-3.4.0.9/MANIFEST000444001750001750 223611432067074 15371 0ustar00ckuelkerckuelker000000000000bin/cipux_rpc_list Build.PL Changes doc/dep/depgraph.dot doc/dep/depgraph.png doc/dep/depgraph.svg doc/example/bin/expl_rpc_login_session_logout doc/example/bin/expl_rpc_ping doc/example/bin/expl_rpc_session doc/example/bin/expl_rpc_task_create_destroy doc/example/bin/expl_rpc_task_create_destroy_two_times doc/example/bin/expl_rpc_task_list doc/example/bin/expl_rpc_task_member doc/example/bin/expl_rpc_task_sum doc/example/README doc/tecdoc/cipux_xml_rpc_server_and_client.tex doc/tecdoc/cipux_xml_rpc_server_and_client_fr.tex doc/tecdoc/fr/php.txt doc/tecdoc/rpc.tex doc/tecdoc/rpc_fr.tex doc/tecdoc/tecdoc_preamble.tex etc/cipux/stunnel-cert.conf etc/cipux/stunnel.conf etc/cipux/stunnel/readme.txt etc/init.d/cipux-rpcd etc/init.d/cipux-rpcdr lib/CipUX/RPC.pm lib/CipUX/RPC/Server.pm lib/CipUX/RPC/Server/Daemon.pm lib/CipUX/RPC/Test/Client.pm Makefile.PL MANIFEST This list of files MANIFEST.SKIP META.yml README sbin/cipux_mkcertkey sbin/cipux_rpc_test_client sbin/cipux_rpc_test_repetition sbin/cipux_rpcd sbin/cipux_rpcdr t/00.load.t t/leaktrace.t t/perlcritic.t t/perlcritic_cpan.t t/perlcriticrc t/pod-coverage.t t/pod.t t/refcount.t usr/share/cipux/etc/cipux-rpc.ini CipUX-RPC-3.4.0.9/Build.PL000444001750001750 447111432067074 15537 0ustar00ckuelkerckuelker000000000000use strict; use warnings; use Module::Build::CipUX; use version; our $VERSION = qv('3.4.0.9'); my $builder = Module::Build::CipUX->new( module_name => 'CipUX::RPC', license => 'gpl2', dist_author => 'Christian Kuelker ', dist_version => $VERSION, dist_abstract => 'CipUX XML-RPC server', # create_makefile_pl => 'traditional', # create_readme => 1, # verbose => 1, installdirs => 'vendor', meta_merge => { resources => { homepage => q(http://www.cipux.org), }, }, recommends => { 'Test::Perl::Critic' => 0, 'Readonly::XS' => 0, 'Test::Pod::Coverage' => 0, }, build_requires => { 'Test::More' => 0, 'Test::Pod' => '1.14', 'Test::Pod::Coverage' => '1.04', 'Module::Build::CipUX' => '0.3.0', 'Test::Refcount' => 0, 'Test::LeakTrace' => 0, }, requires => { 'Authen::Simple::PAM' => 0, 'Authen::Simple::Password' => 0, 'Carp' => 0, 'CipUX' => '3.4.0.4', 'CipUX::RBAC::Simple' => '3.4.0.0', 'CipUX::Task' => '3.4.0.6', 'Class::Std' => '0.0.9', 'Cwd' => 0, 'Data::Dumper' => 0, 'Date::Manip' => 0, 'English' => 0, 'Fatal' => 0, 'File::stat' => 0, 'Frontier::Client' => 0, 'Frontier::Daemon' => 0, 'Frontier::RPC2' => 0, 'Getopt::Long' => 0, 'List::MoreUtils' => 0, 'Log::Log4perl' => 0, 'Log::Dispatch' => 0, 'Pod::Usage' => 0, 'Readonly' => 0, 'Ticket::Simple' => 0, 'version' => 0, }, # ADD to BUILD target stunnel_files => { 'etc/cipux/stunnel/readme.txt' => 'stunnel/readme.txt', }, # ADD to INSTALL target install_path => { stunnel => 'etc/cipux/stunnel', }, add_to_cleanup => ['CipUX-RPC-*'], ); # new BUILD target $builder->add_build_element('stunnel'); $builder->create_build_script(); CipUX-RPC-3.4.0.9/README000444001750001750 136211432067074 15117 0ustar00ckuelkerckuelker000000000000CipUX-RPC version 3.4.0.6 XML-RPC server for CipUX::Task. INSTALLATION To install this module, preferably run the following commands: perl Build.PL ./Build ./Build test ./Build install DEPENDENCIES Authen::Simple::PAM Authen::Simple::Password Carp CipUX CipUX::RBAC::Simple CipUX::Task Class::Std Cwd Data::Dumper Date::Manip English Fatal File::stat Frontier::Client Frontier::Daemon Frontier::RPC2 Getopt::Long List::MoreUtils Log4perl Pod::Usage POSIX Readonly Ticket::Simple version COPYRIGHT AND LICENCE Copyright (C) 2007 - 2009, Christian Kuelker This library is licensed under the GNU General Public License - GNU GPL - version 2 or (at your opinion) any later version. CipUX-RPC-3.4.0.9/META.yml000444001750001750 264311432067074 15513 0ustar00ckuelkerckuelker000000000000--- abstract: 'CipUX XML-RPC server' author: - 'Christian Kuelker ' build_requires: Module::Build::CipUX: v0.3.0 Test::LeakTrace: 0 Test::More: 0 Test::Pod: 1.14 Test::Pod::Coverage: 1.04 Test::Refcount: 0 configure_requires: Module::Build: 0.36 generated_by: 'Module::Build version 0.3607' license: gpl2 meta-spec: url: http://module-build.sourceforge.net/META-spec-v1.4.html version: 1.4 name: CipUX-RPC provides: CipUX::RPC: file: lib/CipUX/RPC.pm version: v3.4.0.9 CipUX::RPC::Server: file: lib/CipUX/RPC/Server.pm version: v3.4.0.9 CipUX::RPC::Server::Daemon: file: lib/CipUX/RPC/Server/Daemon.pm version: v3.4.0.9 CipUX::RPC::Test::Client: file: lib/CipUX/RPC/Test/Client.pm version: v3.4.0.9 recommends: Readonly::XS: 0 Test::Perl::Critic: 0 Test::Pod::Coverage: 0 requires: Authen::Simple::PAM: 0 Authen::Simple::Password: 0 Carp: 0 CipUX: v3.4.0.4 CipUX::RBAC::Simple: v3.4.0.0 CipUX::Task: v3.4.0.6 Class::Std: v0.0.9 Cwd: 0 Data::Dumper: 0 Date::Manip: 0 English: 0 Fatal: 0 File::stat: 0 Frontier::Client: 0 Frontier::Daemon: 0 Frontier::RPC2: 0 Getopt::Long: 0 List::MoreUtils: 0 Log::Dispatch: 0 Log::Log4perl: 0 Pod::Usage: 0 Readonly: 0 Ticket::Simple: 0 version: 0 resources: homepage: http://www.cipux.org license: http://opensource.org/licenses/gpl-2.0.php version: v3.4.0.9 CipUX-RPC-3.4.0.9/Makefile.PL000444001750001750 17111432067074 16166 0ustar00ckuelkerckuelker000000000000use Module::Build::Compat; Module::Build::Compat->run_build_pl(args => \@ARGV); Module::Build::Compat->write_makefile(); CipUX-RPC-3.4.0.9/Changes000444001750001750 1163211432067074 15553 0ustar00ckuelkerckuelker000000000000Revision history for CipUX-RPC 3.4.0.9 2010-08-16T00:32:34 - changes: * add README to doc/example to explain the content * fix bug 'add user a to role group of user b if creating user a,b' by using one task objects per action. * new example expl_rpc_task_create_destroy_two_times - contributor: Harald Meyer (filing bug report during Skolelinux YoungsterMeeting 2010) - version created by: Christian Kuelker 3.4.0.8 2010-02-17T19:04:08 - changes: * some typos fixed in documentation * new examle for CipUX::RPC::Client sub rpc_session in expl_rpc_login_session_logout * tighten licence specification to make META.yml happy * support for cipux_cat_web_module * increase possible leak level from 4 to 5 * cleanup code of cipux_task_rpc_test_client * cipux_task_rpc_test_client use now CipUX::RPC::Client * changing default ttl to 600 sec * change inernal usage of specific attribute to the "value" attribute in Client.pm - contributor: Kurt Gramlich (typo in documentation) Jonas Smedegaard (typo in documentation) Christian Kuelker - version created by: Christian Kuelker 3.4.0.7 2009-12-19T20:32:11 - changes: * add login and logout messages for CAT to syslog * add access messages to syslog (syslog featrue) * new dependency to Log::Dispatch * drop dependency to Digest::MD5 * uses Ticket::Simple, as possible ticket engine. This will make it easier to switch to different mechanism (if needed). * add dependency to Ticket::Simple * update leaktrace.t * add doc directory with (a) Perl programming examples and (b) documentation "The CipUX XML-RPC server" - contributor: Jean-Chales Siegel (docu) Jochen Breuer (docu) Christian Kuelker - version created by: Christian Kuelker 3.4.0.6 2009-10-11T16:25:04 - changes: * change leaktrace test to match Etch requirements - contributor: Christian Kuelker - version created by: Christian Kuelker 3.4.0.5 2009-10-10T13:50:55 - changes: * Use default value '0 1 6' (not '1') for LSB Default-Stop hint in sysV scripts. * add ltarget to rpc header (helps listings) * remove code for handling PID file and forking * remove dependency to POSIX, because forking and detaching will be done init script * init script handles PID file and detaching - contributor: Jonas Smedegaard Christian Kuelker - version created by: Christian Kuelker 3.4.0.4 2009-09-07T18:10:52 - changes: * add flexible cache directory to some constructors and tests - contributor: Christian Kuelker - version created by: Christian Kuelker 3.4.0.3 2009-09-03T14:35:54 - changes: * return "no access" if empty login * regarding a suggestion from Ulich P. Klein renameing task cipux_task_change_own_password_on_command_line to cipux_task_change_own_password_interactive - contributor: Christian Kuelker - version created by: Christian Kuelker 3.4.0.2 2009-07-04T23:01:11 - changes: * move location for cache from /tmp to /var/cache * drop chown root:root /tmp/cipux * drop chmod 0750 /tmp/cipux * use central sub to create cache dir - version created by: Christian Kuelker 3.4.0.1 2009-04-23T17:53:55 - changes: * make support for cipux_task_change_own_password_on_command_line more visible - version created by: Christian Kuelker 3.4.0.0 2009-04-18T22:32:23 - version created by: Christian Kuelker 3.002016 2007-09-11T13:44:22 - version created by: Christian Kuelker 3.002015 Fri Jun 08 22:24:25 2007 - original version; created by h2xs 1.23 with options -v 3.002015 -XA -n RPC CipUX-RPC-3.4.0.9/MANIFEST.SKIP000444001750001750 24411432067074 16113 0ustar00ckuelkerckuelker000000000000-stamp$ \.orig$ \.bak$ \.swp$ \.svn _build blib Build$ \.ptkdb$ .deb$ .build$ .changes$ .upload$ .asc$ .dsc$ .tar.gz$ .cvsignore debian/files$ \..*\~$ ^MYMETA.yml$ CipUX-RPC-3.4.0.9/lib000755001750001750 011432067074 14646 5ustar00ckuelkerckuelker000000000000CipUX-RPC-3.4.0.9/lib/CipUX000755001750001750 011432067074 15636 5ustar00ckuelkerckuelker000000000000CipUX-RPC-3.4.0.9/lib/CipUX/RPC.pm000444001750001750 1443011432067074 16777 0ustar00ckuelkerckuelker000000000000# +==========================================================================+ # || CipUX::RPC || # || || # || CipUX RPC Basis Class || # || || # || Copyright (C) 2007 - 2009 by Christian Kuelker || # || || # || License: GNU GPL - GNU General Public License - version 2 || # || or (at your opinion) any later version. || # || || # +==========================================================================+ # ID: $Id$ # Revision: $Revision$ # Head URL: $HeadURL$ # Date: $Date$ # Source: $Source$ package CipUX::RPC; use 5.008001; use strict; use warnings; use Class::Std; use base qw(CipUX); use Carp; use Data::Dumper; use English qw( -no_match_vars); use Log::Log4perl qw(:easy); use Readonly; { # BEGIN CLASS use version; our $VERSION = qv('3.4.0.9'); use re 'taint'; # Keep data captured by parens tainted delete @ENV{qw(PATH IFS CDPATH ENV BASH_ENV)}; # Make %ENV safe # +======================================================================+ # || CONST || # +======================================================================+ Readonly::Scalar my $EMPTY_STRING => q{}; # +======================================================================+ # || OBJECT || # +======================================================================+ my %rpc_cfg : ATTR( :get ); # +======================================================================+ # || GLOBAL || # +======================================================================+ # +======================================================================+ # || CONSTRUCTOR || # +======================================================================+ sub BUILD { # +------------------------------------------------------------------+ # | API my ( $self, $obj_id, $arg_r ) = @_; # add prefix for cfg, if needed my $pref = exists $arg_r->{pref} ? $self->l( $arg_r->{pref} ) : $EMPTY_STRING; my $cache_dir = exists $arg_r->{cache_dir} ? $self->l( $arg_r->{cache_dir} ) : $EMPTY_STRING; # +------------------------------------------------------------------+ # | prepare # +------------------------------------------------------------------+ # | main $rpc_cfg{$obj_id} = $self->cfg( { 'sub' => 'rpc', pref => $pref, cache_dir => $cache_dir } ); # +------------------------------------------------------------------+ # | API return; } # +======================================================================+ # || DESTRUCTOR || # +======================================================================+ sub DEMOLISH { # +------------------------------------------------------------------+ # | API my ( $self, $ident ) = @_; # +------------------------------------------------------------------+ # | main delete $rpc_cfg{$ident}; # +------------------------------------------------------------------+ # | API return; } # +======================================================================+ # || open module features || # +======================================================================+ } # END INSIDE-OUT CLASS 1; __END__ =pod =head1 NAME CipUX::RPC - RPC server base class for CipUX =head1 VERSION version 3.4.0.9 =head1 SYNOPSIS use CipUX::RPC; =head1 DESCRIPTION Provides the functions for CipUX RPC server. =head1 ABSTRACT CipUX::RPC is a generic abstract class, which can be used by other classes or scripts. =head1 SUBROUTINES/METHODS The following methods are available in the CipUX::RPC class. =head2 BUILD See new. =head2 DEMOLISH Destructor call. =head2 new Constructor B my $cipux_rpc = CipUX::RPC->new({}); my $cfg = '/etc/cipux/cipux-rpc.ini'; my $cipux_rpc = CipUX::RPC->new({pref=>$cfg}); B I Configuration files may be provided for convenience but is not needed. If no configuration file was provided the default configuration file will be loaded. This is meant to work in a single server scenario and multi server settings. =head1 DIAGNOSTICS None. =head1 CONFIGURATION AND ENVIRONMENT CipUX::RPC do not need environemnt variables. It need and provid the CipUX XML-RPC server configration file. =head1 DEPENDENCIES Carp CipUX Class::Std Data::Dumper English Log::Log4perl Readonly =head1 INCOMPATIBILITIES Not known. =head1 BUGS AND LIMITATIONS Not known. =head1 SEE ALSO See the CipUX webpage and the manual at L See the mailing list L =head1 AUTHOR Christian Kuelker Echristian.kuelker@cipworx.orgE =head1 LICENSE AND COPYRIGHT Copyright (C) 2007 - 2009 by Christian Kuelker This program 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, or (at your option) any later version. This program 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 this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA =cut CipUX-RPC-3.4.0.9/lib/CipUX/RPC000755001750001750 011432067074 16262 5ustar00ckuelkerckuelker000000000000CipUX-RPC-3.4.0.9/lib/CipUX/RPC/Server.pm000444001750001750 23005711432067074 20272 0ustar00ckuelkerckuelker000000000000# +==========================================================================+ # || CipUX::RPC::Server || # || || # || CipUX RPC Server Class || # || || # || Copyright (C) 2007 - 2010 by Christian Kuelker || # || || # || License: GNU GPL - GNU general public license - version 2 || # || or (at your opinion) any later version. || # || || # +==========================================================================+ # ID: $Id$ # Revision: $Revision$ # Head URL: $HeadURL$ # Date: $Date$ # Source: $Source$ package CipUX::RPC::Server; use 5.008001; use strict; use warnings; use utf8; use Authen::Simple::PAM; use Authen::Simple::Password; use Carp; use CipUX 3.4.0.4; ##no critic use CipUX::Task; use CipUX::RBAC::Simple; use Class::Std; use Data::Dumper; use English qw( -no_match_vars); use Frontier::Daemon; use Frontier::RPC2; use List::MoreUtils qw(any none); use Log::Dispatch; use Log::Log4perl qw(get_logger :levels); use Readonly; use Ticket::Simple; use base qw(CipUX::RPC); { # BEGIN CLASS # CONSTRUCTOR # DESTRUCTOR # PRIVATE METHODS # * privacy cathegory 1 # - check_authentication # - is_ticket_bad # * privacy cathegory 2 # - signal_handler # - answer_requests # - check_access_to_task # - check_access_to_rpc_intern # - check_access_to_cat_module # - error # - evaluate_access # - update_task # - update_cat_module # PUBLIC METHODS # - get_config # - rpc_list_functions # - rpc_start # XML-RPC METHODS # - ping # - version # - sum # - login # - logout # - session # - ttl # - rpc_task # - rpc_info # - rpc_intern use version; our $VERSION = qv('3.4.0.9'); use re 'taint'; # Keep data captured by parens tainted delete @ENV{qw(PATH IFS CDPATH ENV BASH_ENV)}; # Make %ENV safe use vars qw($cache_dir); # +======================================================================+ # || CONST || # +======================================================================+ Readonly::Scalar my $EMPTY_STRING => q{}; Readonly::Scalar my $SCRIPT => 'CipUX::RPC::Server'; Readonly::Scalar my $HEADER_HREF => { 'cipux_version' => '3.4.0.0', 'server_name' => 'cipux_rpcd', 'server_version' => '3.4.0.0', 'rpc_version' => '2.0', 'server_key' => $EMPTY_STRING, 'server_cred' => $EMPTY_STRING, 'gmt_time' => time, }; Readonly::Scalar my $A_SHORT_WHILE => 3; # in sec Readonly::Scalar my $INTERN_ADMIN_GROUP => 'admin'; # last resort Readonly::Array my @ADMIN_GROUP_CLIENT => qw(cipux_cat_web_module cipux_rpc_test_client cipux_rpc_test_repetition cipuxpasswd); Readonly::Array my @RPC_INTERN_CMD => qw(rpc_intern); Readonly::Array my @RPC_INTERN_SUBCMD => qw(flush cat_module_cache_size rpc_intern_cache_size task_cache_size user_task_access user_task_access_survey user_cat_module_access user_cat_module_access_survey user_rpc_intern_access user_rpc_intern_access_survey ); # +======================================================================+ # || OBJECT || # +======================================================================+ # +======================================================================+ # || GLOBAL || # +======================================================================+ my $cipux_task = undef; my $cipux_task_list_ar = undef; my $rpc_cfg_hr = undef; # undef means: load it at the first time. my $rpc = undef; my $rbac = undef; my $ts = undef; my $time_to_die = 0; # shutdown server with SIG handler # +======================================================================+ # || CONSTRUCTOR || # +======================================================================+ sub BUILD { # +------------------------------------------------------------------+ # | API my ( $self, $ident, $arg_r ) = @_; $cache_dir = exists $arg_r->{cache_dir} ? $self->l( $arg_r->{cache_dir} ) : $EMPTY_STRING; # +------------------------------------------------------------------+ # | main my $cipux_task = CipUX::Task->new( { cache_dir => $cache_dir } ); $cipux_task_list_ar = $cipux_task->list_task(); $rpc = CipUX::RPC->new( { cache_dir => $cache_dir } ); $rbac = CipUX::RBAC::Simple->new(); my $ttl = $self->get_config('xml_rpc_ticket_ttl'); $ts = Ticket::Simple->new( { ttl => $ttl } ); # +------------------------------------------------------------------+ # | API return; } # +======================================================================+ # || DESTRUCTOR || # +======================================================================+ sub DEMOLISH { # +------------------------------------------------------------------+ # | API my ( $self, $ident ) = @_; # +------------------------------------------------------------------+ # | main undef $cipux_task; undef $cipux_task_list_ar; undef $rpc_cfg_hr; undef $rpc; undef $rbac; undef $time_to_die; undef $ts; # +------------------------------------------------------------------+ # | API return; } # +======================================================================+ # || PRIVATE functions to the server (also not exported via XML-RPC) || # +======================================================================+ # +----------------------------------------------------------------------+ # | PRIVACY CATEGORY 1 (have to keep them private) | # +----------------------------------------------------------------------+ # +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~+ # : check_authentication : # +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~+ sub check_authentication : PRIVATE { # +------------------------------------------------------------------+ # | API my ( $self, $arg_r ) = @_; my $login = exists $arg_r->{login} ? $self->l( $arg_r->{login} ) : $self->perr('login'); my $password = exists $arg_r->{password} ? $self->l( $arg_r->{password} ) : $self->perr('password'); # +------------------------------------------------------------------+ # | prepare my $logger = get_logger(__PACKAGE__); # unsuccessfully authentication return 0 (FALSE) my $authenticated = 0; # check for empty password, return 0 (FALSE) if empty if ( $password eq $EMPTY_STRING ) { $logger->debug('Empty password'); $logger->info("authentication not sucessfully for [$login]"); # unsuccessfully authentication return 0 (FALSE) $authenticated = 0; return $authenticated; # we do not want further tests } $logger->debug('using PAM to authenticate login'); my $pam = Authen::Simple::PAM->new( service => 'password' ); if ( $pam->authenticate( $login, $password ) ) { # successfully authentication return 1 (TRUE) $logger->info("authentication sucessfully for [$login]"); $authenticated = 1; } # +------------------------------------------------------------------+ # | API return $authenticated; } # +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~+ # : is_ticket_bad : # +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~+ sub is_ticket_bad : PRIVATE { # +------------------------------------------------------------------+ # | API my ( $self, $arg_r ) = @_; my $login = exists $arg_r->{login} ? $self->l( $arg_r->{login} ) : $self->perr('login'); my $ticket = exists $arg_r->{ticket} ? $self->l( $arg_r->{ticket} ) : $self->perr('ticket'); my $answer_hr = exists $arg_r->{answer_hr} ? $self->h( $arg_r->{answer_hr} ) : $self->perr('answer_hr'); # +------------------------------------------------------------------+ # | prepare my $logger = get_logger(__PACKAGE__); $logger->debug( 'got login: ', $login ); $logger->debug( 'got ticket: ', $ticket ); $logger->debug( 'got answer_hr: ', 'not printed' ); # +------------------------------------------------------------------+ # | main if ( not $ts->is_ticket_valid_now( { login => $login, ticket => $ticket } ) ) { $logger->warn("ticket [$ticket] is not valid for [$login] now"); my $msg = 'Your ticket is not valid!'; $msg .= ' This can have serveral reasons:'; $msg .= ' (1) the ticket expired. (2) an error in programming '; $msg .= ' (3) you never logged in'; # reset cookie => print login dialog $answer_hr->{ticket} = 'test'; $answer_hr->{login} = 'test'; $answer_hr->{msg} = $msg; $answer_hr->{problem} = 2; # NO TIME $answer_hr->{ltarget} = 'NULL'; return { result => 1, answer_hr => $answer_hr }; } # OK, time and ticket an SEED match, must be ours! $answer_hr->{msg} = $EMPTY_STRING; $answer_hr->{problem} = 0; # no problem $logger->debug('Is the ticket bad? NO!! Ticket is not bad.'); $logger->debug('problem 0: no problem'); $logger->warn("ticket [$ticket] is good for [$login]"); # +------------------------------------------------------------------+ # | API return { result => 0, answer_hr => $answer_hr }; } ## end sub is_ticket_bad # +----------------------------------------------------------------------+ # | PRIVACY CATEGORY 2 (might change to public if needed) | # +----------------------------------------------------------------------+ # +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~+ # : signal_handler : # +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~+ sub signal_handler : PRIVATE { # +------------------------------------------------------------------+ # | prepare my $logger = get_logger(__PACKAGE__); $time_to_die = 1; # +------------------------------------------------------------------+ # | API return; } ## end sub signal_handler # +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~+ # : answer_requests : # +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~+ sub answer_requests : PRIVATE { my ( $self, $arg_r ) = @_; my $logger = get_logger(__PACKAGE__); my $port = exists $arg_r->{port} ? $self->l( $arg_r->{port} ) : $self->perr('port'); my $address = exists $arg_r->{address} ? $self->l( $arg_r->{address} ) : $self->perr('address'); my $reuse = exists $arg_r->{reuse} ? $self->l( $arg_r->{reuse} ) : $self->perr('reuse'); my $proto = exists $arg_r->{proto} ? $self->l( $arg_r->{proto} ) : $self->perr('proto'); my $meth_hr = exists $arg_r->{meth_hr} ? $self->h( $arg_r->{meth_hr} ) : $self->perr('meth_hr'); my $task_hr = exists $arg_r->{task_hr} ? $self->h( $arg_r->{task_hr} ) : $self->perr('task_hr'); # +------------------------------------------------------------------+ # | main $logger->debug( 'port: ', $port ); $logger->debug( 'address: ', $address ); $logger->debug( 'reuse: ', $reuse ); $logger->debug( 'proto: ', $proto ); $logger->debug( 'meth_hr: ', $meth_hr ); $logger->debug( 'task_hr: ', $task_hr ); my $z = 0; $logger->debug( 'try to bind to LocalAddr: ', $address ); $logger->debug( 'using LocalPort: ', $port ); my $daemon = 0; while ( not $time_to_die ) { sleep $A_SHORT_WHILE; my $msg = 'Running - if this value counts up continiously,'; $msg .= 'the server did not start correctly. Probably '; $msg .= 'the port is in use. If you see this message only '; $msg .= 'one time everything is OK.'; $logger->info( $msg, $z ); $z++; # LocalPort => "https(8001)", # LocalAddr => "127.0.0.1:8001", $daemon = Frontier::Daemon->new( LocalPort => "https($port)", # 8001 LocalAddr => "$address:$port", # localhost:8001 ReuseAddr => $reuse, # 1, Proto => $proto, # tcp methods => $meth_hr # href to sub refs ); # not implemented for Debian sarge # ReusePort => 1, # On plain debian there is also no support compiled in: # "Your vendor has not defined Socket macro SO_REUSEPORT, # used at /usr/lib/perl/5.8/IO/Socket/INET.pm line 160" $logger->debug( 'daemon: ', $daemon ); } ## end while ( not $time_to_die ) # +------------------------------------------------------------------+ # | API return 1; } ## end sub answer_requests # +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~+ # : check_access_to_task : # +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~+ sub check_access_to_task : PRIVATE { # +------------------------------------------------------------------+ # | API my ( $self, $arg_r ) = @_; my $login = exists $arg_r->{login} ? $self->l( $arg_r->{login} ) : $self->perr('login'); my $cmd = exists $arg_r->{cmd} ? $self->l( $arg_r->{cmd} ) : $self->perr('cmd'); # +------------------------------------------------------------------+ # | main my $logger = get_logger(__PACKAGE__); if ( $login eq $EMPTY_STRING ) { $logger->info("Access DENIED for [$login] to [$cmd]"); $logger->debug('Empty login: Access DENIED'); return 0; } if ( $cmd eq 'cipux_task_sum' ) { $logger->info("Access GRANTED for [$login] to [$cmd]!"); return 1; } elsif ( $rbac->access_to_task( { task => $cmd, user => $login } ) ) { $logger->info("Access GRANTED for [$login] to [$cmd]"); return 1; } else { $logger->info("Access DENIED for [$login] to [$cmd]"); $logger->debug('Empty login: Access DENIED'); } # +------------------------------------------------------------------+ # | API return 0; } # +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~+ # : check_access_to_cat_module : # +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~+ sub check_access_to_cat_module : PRIVATE { # +------------------------------------------------------------------+ # | API my ( $self, $arg_r ) = @_; my $login = exists $arg_r->{login} ? $self->l( $arg_r->{login} ) : $self->perr('login'); my $cmd = exists $arg_r->{cmd} ? $self->l( $arg_r->{cmd} ) : $self->perr('cmd'); # +------------------------------------------------------------------+ # | main my $logger = get_logger(__PACKAGE__); $logger->debug("login [$login]"); $logger->debug("cmd [$cmd]"); if ( $cmd eq 'index.cgi' ) { return 1; } elsif ( $rbac->access_to_cat_module( { cat_module => $cmd, user => $login } ) ) { $logger->debug('ACCESS'); return 1; } else { $logger->debug('NO ACCESS'); } # +------------------------------------------------------------------+ # | API return 0; } # +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~+ # : check_access_to_rpc_intern : # +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~+ sub check_access_to_rpc_intern : PRIVATE { # +------------------------------------------------------------------+ # | API my ( $self, $arg_r ) = @_; my $login = exists $arg_r->{login} ? $self->l( $arg_r->{login} ) : $self->perr('login'); my $cmd = exists $arg_r->{cmd} ? $self->l( $arg_r->{cmd} ) : $self->perr('cmd'); # +------------------------------------------------------------------+ # | main my $logger = get_logger(__PACKAGE__); $logger->debug("login [$login]"); $logger->debug("cmd [$cmd]"); my $role = ( $self->get_config('xml_rpc_intern_admin_group') ) ? $self->get_config('xml_rpc_intern_admin_group') : $INTERN_ADMIN_GROUP; $logger->debug("role [$role]"); if ( none {m/^$cmd$/smx} @RPC_INTERN_CMD ) { $logger->debug("rpc_intern command not valid [$cmd]"); } elsif ( ( any {m/^$cmd$/smx} @RPC_INTERN_CMD ) and $rbac->access_to_rpc_intern( { role => $role, user => $login } ) ) { $logger->debug('ACCESS'); return 1; } else { $logger->debug('NO ACCESS'); } # +------------------------------------------------------------------+ # | API return 0; } # +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~+ # : error : # +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~+ sub error : PRIVATE { # +------------------------------------------------------------------+ # | API layer 1 my ( $self, $arg_r ) = @_; my $logger = get_logger(__PACKAGE__); my $login = exists $arg_r->{login} ? $self->l( $arg_r->{login} ) : $self->perr('login'); my $ticket = exists $arg_r->{ticket} ? $self->l( $arg_r->{ticket} ) : $self->perr('ticket'); my $cmd = exists $arg_r->{cmd} ? $self->l( $arg_r->{cmd} ) : $self->perr('cmd'); my $msg = "No access for [$login] to [$cmd]"; # answer my $answer_hr = { header_hr => $HEADER_HREF, cmd => $cmd, login => $login, ticket => $ticket, status => 'FALSE', msg => $msg, type => 'href', cmdres_r => {}, ltarget => 'NULL', }; $logger->debug( '> answer_hr', { filter => \&Dumper, value => $answer_hr } ); # +------------------------------------------------------------------+ # | API return $answer_hr; } # +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~+ # : evaluate_access : # +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~+ sub evaluate_access : PRIVATE { # +------------------------------------------------------------------+ # | API my ( $self, $arg_r ) = @_; my $login = exists $arg_r->{login} ? $self->l( $arg_r->{login} ) : $self->perr('login'); my $realm = exists $arg_r->{realm} ? $self->l( $arg_r->{realm} ) : $self->perr('realm'); my $param_hr = exists $arg_r->{param_hr} ? $self->h( $arg_r->{param_hr} ) : $self->perr('param_hr'); # +------------------------------------------------------------------+ # | main my $check_access_disp_hr = { 'task' => \&check_access_to_task, 'cat_module' => \&check_access_to_cat_module, 'rpc_intern' => \&check_access_to_rpc_intern, }; my $logger = get_logger(__PACKAGE__); $logger->debug("login [$login]"); $logger->debug("realm [$realm]"); if ( not exists $check_access_disp_hr->{$realm} ) { croak "Realm [$realm] do not exists in dispatch table!\n"; } # 'entity' => 'task', # 'to' => 'cipadmin', # 'to_ar' => [ # 'cipux_task_list_user_accounts' # ], # 'subcmd' => 'user_task_access', # 'rcpmode' => 'rpc_intern', # 'scope' => 'single' $logger->debug("login [$login]"); my $cmdres_r = {}; my $subcmd = ( exists $param_hr->{subcmd} ) ? $param_hr->{subcmd} : return $cmdres_r; # we do not trust the "from" param. my $from = ( $subcmd eq 'task_access' or $subcmd eq 'task_access_survey' ) ? $login : ( $subcmd eq 'user_task_access' or $subcmd eq 'user_task_access_survey' or $subcmd eq 'user_cat_module_access' or $subcmd eq 'user_cat_module_access_survey' or $subcmd eq 'user_rpc_intern_access' or $subcmd eq 'user_rpc_intern_access_survey' and exists $param_hr->{from} ) ? $param_hr->{from} : $login; $logger->debug("from [$from]"); my $to_ar = exists $param_hr->{to_ar} ? $param_hr->{to_ar} : []; my %access = (); foreach my $to ( @{$to_ar} ) { $logger->debug("examine to [$to]"); my $disp = $check_access_disp_hr->{$realm}; my $r = $self->$disp( { login => $from, cmd => $to } ); $logger->debug("access result [$to] for [$from]: [$r]"); $access{$to} = $r; } $cmdres_r = { from => $from, access_hr => \%access, }; # +------------------------------------------------------------------+ # | API return $cmdres_r; } # +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~+ # : update_task : # +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~+ sub update_task : PRIVATE { # +------------------------------------------------------------------+ # | API my ( $self, $arg_r ) = @_; # +------------------------------------------------------------------+ # | prepare my $logger = get_logger(__PACKAGE__); my %task = (); my $cipux_task = CipUX::Task->new( { cache_dir => $cache_dir } ); my $return_r = $cipux_task->task( { script => __PACKAGE__, task => 'cipux_task_list_tasks', mode => 'rpc', object => undef, attr_hr => {}, } ); # $return_r = { # 'taskres_r' => { # '' => { # 'cn' => [ # '' # ] # } # }, # 'status' => 'OK', # 'type' => 'href' # }; if ( exists $return_r->{status} and $return_r->{status} eq 'OK' ) { my @task = keys %{ $return_r->{taskres_r} }; %task = map { $_ => 1 } @task; } else { confess 'Can not get tasks by cipux_task_list_tasks!'; } foreach my $task ( sort @{$cipux_task_list_ar} ) { if ( $task{$task} ) { $logger->debug("task exists [$task]"); } else { $logger->debug("task [$task] do not exist"); my $return_r = $cipux_task->task( { script => __PACKAGE__, task => 'cipux_task_register_task', mode => 'rpc', object => $task, attr_hr => {}, } ); if ( not( exists $return_r->{status} and $return_r->{status} eq 'OK' ) ) { confess "failure cipux_task_register_task [$task]!"; } } } # +------------------------------------------------------------------+ # | API return; } # +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~+ # : update_cat : # +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~+ sub update_cat_module : PRIVATE { # +------------------------------------------------------------------ # | API my ( $self, $arg_r ) = @_; # +------------------------------------------------------------------+ # | prepare my $logger = get_logger(__PACKAGE__); my %cat = (); my $cipux_task = CipUX::Task->new( { cache_dir => $cache_dir } ); my $return_r = $cipux_task->task( { script => __PACKAGE__, task => 'cipux_task_list_cat_modules', mode => 'rpc', object => undef, attr_hr => {}, } ); # $return_r = { # 'taskres_r' => { # '' => { # 'cn' => [ # '' # ] # } # }, # 'status' => 'OK', # 'type' => 'href' # }; if ( exists $return_r->{status} and $return_r->{status} eq 'OK' ) { my @cat = keys %{ $return_r->{taskres_r} }; %cat = map { $_ => 1 } @cat; } else { confess 'Can not get modules by cipux_task_list_cat_modules!'; } foreach my $mod (@ADMIN_GROUP_CLIENT) { if ( $cat{$mod} ) { $logger->debug("module exists [$mod]"); } else { $logger->debug("module [$mod] do not exist"); # default 'admin' (could be other) my $role = ( $self->get_config('xml_rpc_intern_admin_group') ) ? $self->get_config('xml_rpc_intern_admin_group') : $INTERN_ADMIN_GROUP; my $attr_hr = { cipuxName => $mod, cipuxTemplate => 'none', cipuxTemplateDir => 'none', cipuxEntity => 'none', cipuxModality => 'none', cipuxIsModuleArray => 'FALSE', cipuxAuthor => 'same as CipUX', cipuxLicense => 'same as CipUX', cipuxYear => 'same as CipUX', cipuxScript => $mod, cipuxIcon => 'shell.png', cipuxShortDescription => 'CAT on the command line', cipuxIsEnabled => 'yes', cipuxMemberRid => $role, }; my $return_r = $cipux_task->task( { script => __PACKAGE__, task => 'cipux_task_register_cat_module', mode => 'rpc', object => $mod, attr_hr => {}, } ); if ( not( exists $return_r->{status} and $return_r->{status} eq 'OK' ) ) { confess "failure cipux_task_register_cat_module [$mod]!"; } } } # +------------------------------------------------------------------+ # | API return; } # +======================================================================+ # || PUBLIC METHODS || # +======================================================================+ # +----------------------------------------------------------------------+ # | log2syslog | # +----------------------------------------------------------------------+ sub log2syslog { my $message = shift; my $log = Log::Dispatch->new( outputs => [ [ 'Syslog', min_level => 'info', ] ] ); $log->emerg($message); return; } # +----------------------------------------------------------------------+ # | get_config | # +----------------------------------------------------------------------+ sub get_config { # +------------------------------------------------------------------+ # | API my ( $self, $key ) = @_; my $logger = get_logger(__PACKAGE__); $logger->debug("asked for configuration key [$key]"); # +------------------------------------------------------------------+ # | main if ( not defined $rpc_cfg_hr ) { $logger->debug('get_rpc_cfg'); $rpc_cfg_hr = $rpc->get_rpc_cfg(); } my $return = $rpc_cfg_hr->{$key}; # +------------------------------------------------------------------+ # | API return $return; } ## end sub get_config # +---------------------------------------------------------------------+ # | rpc_list_functions | # +---------------------------------------------------------------------+ sub rpc_list_functions { # +------------------------------------------------------------------+ # | API my ( $self, $arg_r ) = @_; my $logger = get_logger(__PACKAGE__); my $scope = exists $arg_r->{scope} ? $self->l( $arg_r->{scope} ) : $self->perr('scope'); # all | # +------------------------------------------------------------------+ # | main $logger->debug( 'scope: ', $scope ); my @cmd = @{$cipux_task_list_ar}; # add generic RPC commands: push @cmd, qw( ping version sum login logout session ttl); push @cmd, qw( task rpc_info rpc_intern ); # +------------------------------------------------------------------+ # | API return @cmd; } # +----------------------------------------------------------------------+ # | rpc_start | # +----------------------------------------------------------------------+ sub rpc_start { # +------------------------------------------------------------------+ # | API my ( $self, $arg_r ) = @_; my $logger = get_logger(__PACKAGE__); # +------------------------------------------------------------------+ # | main my @obj = qw(/var/cache/cipux/cipux-access /var/cache/cipux/cipux-object /var/cache/cipux/cipux-rbac /var/cache/cipux/cipux-rpc /var/cache/cipux/cipux-storage /var/cache/cipux/cipux-task); my $l = unlink @obj; $self->create_cache_dir_if_not_present(); my $msg = 'start server with address'; my $xml_rpc_address = $self->get_config('xml_rpc_address'); $logger->debug( $msg . q{: }, $xml_rpc_address ); if ( not( $xml_rpc_address eq 'localhost' or $xml_rpc_address eq '127.0.0.1' ) ) { my $msg = 'xml_rpc_address other then localhost or 127.0.0.1 '; $msg .= 'are not supported. '; $msg .= 'Please choose a different value in configuration file. '; $msg .= 'The value which was given was'; $self->exc( { msg => $msg, value => $xml_rpc_address } ); } $msg = 'start server with port'; my $xml_rpc_port = $self->get_config('xml_rpc_port'); $logger->debug( $msg . q{: }, $xml_rpc_port ); $msg = 'start server with ticket ttl'; my $xml_rpc_ticket_ttl = $self->get_config('xml_rpc_ticket_ttl'); $logger->debug( $msg . q{: }, $xml_rpc_ticket_ttl ); if ( $xml_rpc_ticket_ttl <= 0 ) { my $msg = 'xml_rpc_ticket_ttl is less or equal then 0'; $msg .= 'Please choose a different value in configuration file.'; $self->exc( { msg => $msg, value => $xml_rpc_address } ); } $msg = 'start server with use_pam'; my $xml_rpc_use_pam = $self->get_config('xml_rpc_use_pam'); $logger->debug( $msg . q{: }, $xml_rpc_use_pam ); $msg = 'start server with pid_file'; my $xml_rpc_pid_file = $self->get_config('xml_rpc_pid_file'); $logger->debug( $msg . q{: }, $xml_rpc_pid_file ); $msg = 'start server with proto'; my $xml_rpc_proto = $self->get_config('xml_rpc_proto'); $logger->debug( $msg . q{: }, $xml_rpc_proto ); $msg = 'start server with reuse'; my $xml_rpc_reuse = $self->get_config('xml_rpc_reuse'); $logger->debug( $msg . q{: }, $xml_rpc_reuse ); # $msg = 'start server with TTL'; # my $xml_rpc_ticket_ttl = $self->get_config('xml_rpc_ticket_ttl'); # $logger->debug($msg.q{: }, $xml_rpc_ticket_ttl ,"\n"); # as everybody, who got killed, we have to sleep 3 seconds # to regenerate our self to awake again ;-) sleep $A_SHORT_WHILE; # check all given tasks. $self->update_task(); # check RPC own CAT modules. $self->update_cat_module(); my @list = $self->rpc_list_functions( { scope => 'all' } ); my $task_hr = {}; foreach my $task (@list) { $task_hr->{$task} = 1; } # methode_hr my $meth_hr = { ping => sub { $self->ping(@_); }, version => sub { $self->version(@_); }, sum => sub { $self->sum(@_); }, login => sub { $self->login(@_); }, logout => sub { $self->logout(@_); }, session => sub { $self->session(@_); }, ttl => sub { $self->ttl(@_); }, task => sub { #my $logger = get_logger(__PACKAGE__); #foreach my $x (@_) { # $logger->debug("X [$x]"); # foreach my $h ( keys %{$x} ) { # $logger->debug("h [$h] -> [$x->{$h}]"); # } #} if ( $self->check_access_to_task(@_) ) { $self->rpc_task(@_); } else { $self->error(@_); } }, rpc_info => sub { $self->rpc_info(@_); }, rpc_intern => sub { if ( $self->check_access_to_rpc_intern(@_) ) { $self->rpc_intern(@_); } else { $self->error(@_); } }, }; $self->answer_requests( { address => $xml_rpc_address, port => $xml_rpc_port, reuse => $xml_rpc_reuse, proto => $xml_rpc_proto, meth_hr => $meth_hr, task_hr => $task_hr, } ); # +------------------------------------------------------------------+ # | API return 1; } ## end sub rpc_start # +======================================================================+ # || XML-RPC METHODS (exported via XML-RPC server) || # +======================================================================+ # +----------------------------------------------------------------------+ # | ping | # +----------------------------------------------------------------------+ sub ping : PRIVATE { # +------------------------------------------------------------------+ # | API layer 1 my ( $self, $arg_r ) = @_; my $logger = get_logger(__PACKAGE__); my $header_hr = exists $arg_r->{header_hr} ? $self->h( $arg_r->{header_hr} ) : $self->perr('header_hr'); my $login = exists $arg_r->{login} ? $self->l( $arg_r->{login} ) : $self->perr('login'); my $ticket = exists $arg_r->{ticket} ? $self->l( $arg_r->{ticket} ) : $self->perr('ticket'); my $cmd = exists $arg_r->{cmd} ? $self->l( $arg_r->{cmd} ) : $self->perr('cmd'); my $param_hr = exists $arg_r->{param_hr} ? $self->h( $arg_r->{param_hr} ) : $self->perr('param_hr'); # +------------------------------------------------------------------+ # | main # answer my $answer_hr = { header_hr => $HEADER_HREF, cmd => 'ping', login => $login, ticket => $ticket, status => 'TRUE', # use this to test if available msg => $EMPTY_STRING, type => 'href', cmdres_r => {}, ltarget => 'NULL', }; # +------------------------------------------------------------------+ # | API return $answer_hr; } ## end sub ping # +----------------------------------------------------------------------+ # | version | # +----------------------------------------------------------------------+ sub version : PRIVATE { # +------------------------------------------------------------------+ # | API layer 1 my ( $self, $arg_r ) = @_; my $logger = get_logger(__PACKAGE__); my $header_hr = exists $arg_r->{header_hr} ? $self->h( $arg_r->{header_hr} ) : $self->perr('header_hr'); my $login = exists $arg_r->{login} ? $self->l( $arg_r->{login} ) : $self->perr('login'); my $ticket = exists $arg_r->{ticket} ? $self->l( $arg_r->{ticket} ) : $self->perr('ticket'); my $cmd = exists $arg_r->{cmd} ? $self->l( $arg_r->{cmd} ) : $self->perr('cmd'); my $param_hr = exists $arg_r->{param_hr} ? $self->h( $arg_r->{param_hr} ) : $self->perr('param_hr'); # +------------------------------------------------------------------+ # | main my $cmdres_r = { cipux_version => '3.4.0.0', server_version => '3.4.0.0', rpc_version => '2.0', }; # answer my $answer_hr = { header_hr => $HEADER_HREF, cmd => 'version', login => $login, ticket => $ticket, status => 'TRUE', msg => $EMPTY_STRING, type => 'href', cmdres_r => $cmdres_r, ltarget => 'NULL', }; # +------------------------------------------------------------------+ # | API return $answer_hr; } ## end sub version # +----------------------------------------------------------------------+ # | sum (alias sum1) | # +----------------------------------------------------------------------+ sub sum : PRIVATE { # +------------------------------------------------------------------+ # | API level 1 my ( $self, $arg_r ) = @_; my $logger = get_logger(__PACKAGE__); my $header_hr = exists $arg_r->{header_hr} ? $self->h( $arg_r->{header_hr} ) : $self->perr('header_hr'); my $login = exists $arg_r->{login} ? $self->l( $arg_r->{login} ) : $self->perr('login'); my $ticket = exists $arg_r->{ticket} ? $self->l( $arg_r->{ticket} ) : $self->perr('ticket'); my $cmd = exists $arg_r->{cmd} ? $self->l( $arg_r->{cmd} ) : $self->perr('cmd'); my $param_hr = exists $arg_r->{param_hr} ? $self->h( $arg_r->{param_hr} ) : $self->perr('param_hr'); my $summand1 = exists $param_hr->{summand1} ? $self->l( $param_hr->{summand1} ) : $self->perr('summand1 in param_hr not arg_r'); my $summand2 = exists $param_hr->{summand2} ? $self->l( $param_hr->{summand2} ) : $self->perr('summand2 in param_hr not arg_r'); # +------------------------------------------------------------------+ # | main $logger->debug( 'arg_r: ', { filter => \&Dumper, value => $arg_r } ); # main my $sum = $summand1 + $summand2; my $cmdres_r = [$sum]; # answer my $answer_hr = { header_hr => $HEADER_HREF, cmd => 'sum', login => $login, ticket => $ticket, status => 'TRUE', msg => $EMPTY_STRING, type => 'aref', cmdres_r => $cmdres_r, ltarget => 'NULL', }; # +------------------------------------------------------------------+ # | API return $answer_hr; } ## end sub sum # +----------------------------------------------------------------------+ # | login | # +----------------------------------------------------------------------+ sub login : PRIVATE { # +------------------------------------------------------------------+ # | API layer 1 my ( $self, $arg_r ) = @_; my $logger = get_logger(__PACKAGE__); my $header_hr = exists $arg_r->{header_hr} ? $self->h( $arg_r->{header_hr} ) : $self->perr('header_hr'); my $login = exists $arg_r->{login} ? $self->l( $arg_r->{login} ) : $self->perr('login'); my $ticket = exists $arg_r->{ticket} ? $self->l( $arg_r->{ticket} ) : $self->perr('ticket'); my $cmd = exists $arg_r->{cmd} ? $self->l( $arg_r->{cmd} ) : $self->perr('cmd'); my $param_hr = exists $arg_r->{param_hr} ? $self->h( $arg_r->{param_hr} ) : $self->perr('param_hr'); my $password = exists $param_hr->{password} ? $self->l( $param_hr->{password} ) : $self->perr('password'); # +------------------------------------------------------------------+ # | main # +------------------------------------------------------------------+ # | debug my $msg = 'we got login'; $logger->debug( $msg . q{: }, $login ); $msg = 'given password was defined - no print here -'; $logger->debug($msg); $msg = 'start use login with ticket ttl'; my $xml_rpc_ticket_ttl = $self->get_config('xml_rpc_ticket_ttl'); $logger->debug( $msg . q{: }, $xml_rpc_ticket_ttl ); # answer my $answer_hr = { header_hr => $HEADER_HREF, cmd => 'login', login => $login, ticket => $ticket, status => 'FALSE', msg => $EMPTY_STRING, type => 'href', cmdres_r => {}, ltarget => 'NULL', }; if ( $password eq $EMPTY_STRING ) { my $msg = 'empty password given.'; $logger->debug($msg); # +---------------------------------------------------------------+ # | API return $answer_hr; } $msg = 'password was given for login'; $logger->debug( $msg . q{: }, $login ); # check if login is authenticated my $is_authenticated = $self->check_authentication( { login => $login, password => $password } ); if ($is_authenticated) { $logger->debug('password is valid for login'); $logger->debug("login is_authenticated: $login"); #$logger->debug("old login ticket: [$ticket]"); # create new ticket, no test my ( $ticket, $valid ) = $ts->create_ticket( { login => $login } ); $ts->store_ticket( { login => $login, ticket => $ticket, valid => $valid } ); #$logger->debug("new login ticket: [$ticket]"); $logger->debug( 'ticket: ', $ticket ); # differnt answer $answer_hr->{status} = 'TRUE'; $answer_hr->{type} = 'href'; $answer_hr->{cmdres_r} = { login => $login, ticket => $ticket, ttl => $xml_rpc_ticket_ttl, }; $answer_hr->{ltarget} = 'NULL'; # +----------------------------------------------------------------+ # | API return $answer_hr; } else { my $msg = 'WARN password is valid NOT for login '; $logger->debug( $msg . q{: }, $login ); # +----------------------------------------------------------------+ # | API return $answer_hr; } # +------------------------------------------------------------------+ # | API return $answer_hr; } ## end sub login # +----------------------------------------------------------------------+ # | logout | # +----------------------------------------------------------------------+ sub logout : PRIVATE { # +------------------------------------------------------------------+ # | API level 1 my ( $self, $arg_r ) = @_; my $logger = get_logger(__PACKAGE__); my $header_hr = exists $arg_r->{header_hr} ? $self->h( $arg_r->{header_hr} ) : $self->perr('header_hr'); my $login = exists $arg_r->{login} ? $self->l( $arg_r->{login} ) : $self->perr('login'); my $ticket = exists $arg_r->{ticket} ? $self->l( $arg_r->{ticket} ) : $self->perr('ticket'); my $cmd = exists $arg_r->{cmd} ? $self->l( $arg_r->{cmd} ) : $self->perr('cmd'); my $param_hr = exists $arg_r->{param_hr} ? $self->h( $arg_r->{param_hr} ) : $self->perr('param_hr'); # +------------------------------------------------------------------+ # | main # answer my $answer_hr = { header_hr => $HEADER_HREF, cmd => 'logout', login => $login, ticket => $ticket, status => 'FALSE', msg => $EMPTY_STRING, type => 'href', cmdres_r => {}, ltarget => 'NULL', }; # check the ticket my $bad_hr = $self->is_ticket_bad( { login => $login, ticket => $ticket, answer_hr => $answer_hr } ); # +------------------------------------------------------------------+ # | API if ( $bad_hr->{result} ) { $logger->debug('bad result'); return $bad_hr->{answer_hr}; } # main $ts->wipe_ticket( { login => $login } ); # answer $answer_hr->{status} = 'TRUE'; $answer_hr->{type} = 'href'; $answer_hr->{cmdres_r} = {}; # +------------------------------------------------------------------+ # | API return $answer_hr; } ## end sub logout # +----------------------------------------------------------------------+ # | session | # +----------------------------------------------------------------------+ sub session : PRIVATE { # +------------------------------------------------------------------+ # | API level 1 my ( $self, $arg_r ) = @_; my $logger = get_logger(__PACKAGE__); my $header_hr = exists $arg_r->{header_hr} ? $self->h( $arg_r->{header_hr} ) : $self->perr('header_hr'); my $login = exists $arg_r->{login} ? $self->l( $arg_r->{login} ) : $self->perr('login'); my $ticket = exists $arg_r->{ticket} ? $self->l( $arg_r->{ticket} ) : $self->perr('ticket'); my $cmd = exists $arg_r->{cmd} ? $self->l( $arg_r->{cmd} ) : $self->perr('cmd'); my $param_hr = exists $arg_r->{param_hr} ? $self->h( $arg_r->{param_hr} ) : $self->perr('param_hr'); # +------------------------------------------------------------------+ # | main # answer my $answer_hr = { header_hr => $HEADER_HREF, cmd => 'session', login => $login, ticket => $ticket, status => 'FALSE', msg => $EMPTY_STRING, type => 'href', cmdres_r => {}, ltarget => 'NULL', }; # check the ticket my $bad_hr = $self->is_ticket_bad( { login => $login, ticket => $ticket, answer_hr => $answer_hr } ); if ( $bad_hr->{result} ) { $logger->debug('bad result'); # +--------------------------------------------------------------+ # | API return $bad_hr->{answer_hr}; } my ( $t, $v ) = $ts->create_ticket( { login => $login } ); $ts->store_ticket( { login => $login, ticket => $t, valid => $v } ); # answer #$answer_hr->{ticket} = $t; $answer_hr->{status} = 'TRUE'; $answer_hr->{type} = 'href'; $answer_hr->{cmdres_r} = { ticket => $t, }; # +------------------------------------------------------------------+ # | API return $answer_hr; } ## end sub session # +----------------------------------------------------------------------+ # | ttl | # +----------------------------------------------------------------------+ sub ttl : PRIVATE { # +------------------------------------------------------------------+ # | API level 1 my ( $self, $arg_r ) = @_; my $logger = get_logger(__PACKAGE__); my $header_hr = exists $arg_r->{header_hr} ? $self->h( $arg_r->{header_hr} ) : $self->perr('header_hr'); my $login = exists $arg_r->{login} ? $self->l( $arg_r->{login} ) : $self->perr('login'); my $ticket = exists $arg_r->{ticket} ? $self->l( $arg_r->{ticket} ) : $self->perr('ticket'); my $cmd = exists $arg_r->{cmd} ? $self->l( $arg_r->{cmd} ) : $self->perr('cmd'); my $param_hr = exists $arg_r->{param_hr} ? $self->h( $arg_r->{param_hr} ) : $self->perr('param_hr'); # +------------------------------------------------------------------+ # | main # answer my $answer_hr = { header_hr => $HEADER_HREF, cmd => 'ttl', login => $login, ticket => $ticket, status => 'FALSE', msg => $EMPTY_STRING, type => 'href', cmdres_r => {}, ltarget => 'NULL', }; # check the ticket my $bad_hr = $self->is_ticket_bad( { login => $login, ticket => $ticket, answer_hr => $answer_hr } ); if ( $bad_hr->{result} ) { $logger->debug('bad result'); # +--------------------------------------------------------------+ # | API return $bad_hr->{answer_hr}; } # main my $ttl = $ts->get_ttl; # answer $answer_hr->{login} = $login; $answer_hr->{ticket} = $ticket; $answer_hr->{status} = 'TRUE'; $answer_hr->{type} = 'href'; $answer_hr->{cmdres_r} = { ttl => $ttl }; # +------------------------------------------------------------------+ # | API return $answer_hr; } ## end sub ttl # +----------------------------------------------------------------------+ # | rpc_task (uses CipUX::Task) | # +----------------------------------------------------------------------+ sub rpc_task : PRIVATE { # +------------------------------------------------------------------+ # | API layer 1 my ( $self, $arg_r ) = @_; my $logger = get_logger(__PACKAGE__); my $header_hr = exists $arg_r->{header_hr} ? $self->h( $arg_r->{header_hr} ) : $self->perr('header_hr'); my $login = exists $arg_r->{login} ? $self->l( $arg_r->{login} ) : $self->perr('login'); my $ticket = exists $arg_r->{ticket} ? $self->l( $arg_r->{ticket} ) : $self->perr('ticket'); my $cmd = exists $arg_r->{cmd} ? $self->l( $arg_r->{cmd} ) : $self->perr('cmd'); my $param_hr = exists $arg_r->{param_hr} ? $self->h( $arg_r->{param_hr} ) : $self->perr('param_hr'); # API layer 2 my $object = $self->l( $param_hr->{object} ) || $EMPTY_STRING; # +------------------------------------------------------------------+ # | main $logger->debug("cmd [$cmd]"); # collect valid tasks my @task_list = $self->rpc_list_functions( { scope => 'all' } ); my %valid_task = map { $_ => 1 } @task_list; $logger->debug( 'param_hr,: ', { filter => \&Dumper, value => $param_hr } ); # answer my $answer_hr = { header_hr => $HEADER_HREF, cmd => $cmd, login => $login, ticket => $ticket, status => 'FALSE', msg => $EMPTY_STRING, type => 'href', cmdres_r => {}, ltarget => 'NULL', }; # check if the task is valid, if not return FALSE if ( not $valid_task{$cmd} ) { my $msg = "Not a valid task $cmd."; $answer_hr->{msg} = $cmd; $logger->debug($cmd); # +--------------------------------------------------------------+ # | API return $answer_hr; } # you have to be logged in only for non test tasks if ( not $cmd eq 'cipux_task_sum' ) { # check the ticket my $bad_hr = $self->is_ticket_bad( { login => $login, ticket => $ticket, answer_hr => $answer_hr } ); if ( $bad_hr->{result} ) { $logger->debug('bad result'); # +----------------------------------------------------------+ # | API return $bad_hr->{answer_hr}; } $logger->debug("create new ticket for $login"); } if ( $cmd eq 'cipux_task_change_own_password_interactive' ) { my $password = ( exists $param_hr->{userPassword} ) ? $param_hr->{userPassword} : ( exists $param_hr->{value} ) ? $param_hr->{value} : $self->random_password(); $param_hr = { object => $login, userPassword => $password, }; } else { # do work foreach my $attr ( sort keys %{$param_hr} ) { if ( ref $param_hr->{$attr} ne 'ARRAY' ) { $param_hr->{$attr} = [ $param_hr->{$attr} ]; } } } my $cipux_task = CipUX::Task->new( { cache_dir => $cache_dir } ); my $return_r = $cipux_task->task( { script => $SCRIPT, task => $cmd, mode => 'rpc', object => $object, attr_hr => $param_hr, } ); # main $answer_hr->{login} = $login; $answer_hr->{ticket} = $ticket; if ( $return_r->{status} eq 'OK' ) { $answer_hr->{status} = 'TRUE'; $answer_hr->{type} = ref $return_r->{taskres_r}; $answer_hr->{cmdres_r} = $return_r->{taskres_r}; $answer_hr->{ltarget} = $return_r->{ltarget}; } if ( ref $answer_hr eq 'HASH' ) { $logger->debug('got a answer_hr hash.'); $logger->debug( 'storage answer_hr ', { filter => \&Dumper, value => $answer_hr } ); } else { $logger->debug('got something else (not a hash) as answer_hr'); } # +------------------------------------------------------------------+ # | API return $answer_hr; } ## end sub rpc_task # +----------------------------------------------------------------------+ # | rpc_info | # +----------------------------------------------------------------------+ sub rpc_info : PRIVATE { # $arg_r => { # 'param_hr' => { # 'source' => 'cipadmin', # 'destination' => 'index.cgi', # 'rbac' => 'access', # 'ticket' => 'test', # 'login' => 'cipadmin' # }, # 'cmd' => 'cipux_rbac', # 'header_hr' => { # 'cipux_version' => '3.4.0.0', # 'client_key' => '', # 'gmt_time' => '1222543779', # 'client_cred' => '', # 'client_name' => 'cipux_cat_web', # 'client_version' => '3.4.0.0', # 'rpc_version' => '2.0' # }, # 'ticket' => 'dummy', # 'login' => 'dummy' # } # +------------------------------------------------------------------+ # | API my ( $self, $arg_r ) = @_; my $header_hr = exists $arg_r->{header_hr} ? $self->h( $arg_r->{header_hr} ) : $self->perr('header_hr'); my $login = exists $arg_r->{login} ? $self->l( $arg_r->{login} ) : $self->perr('login'); my $ticket = exists $arg_r->{ticket} ? $self->l( $arg_r->{ticket} ) : $self->perr('ticket'); my $param_hr = exists $arg_r->{param_hr} ? $self->h( $arg_r->{param_hr} ) : $self->perr('param_hr'); # +------------------------------------------------------------------+ # | main my $logger = get_logger(__PACKAGE__); $logger->debug("> login: $login"); $logger->debug( 'ticket: ', $ticket ); $logger->debug('> header_hr: (not printed)'); $logger->debug( '> param_hr', { filter => \&Dumper, value => $param_hr } ); # check_access_task() # check_access_task_survey() # valid_cat_module_for_user(user, cat_module_list) # evaluate_access(cat_module_list) # nicht? (user, cat_module_list) # check_access_to_task(task) # nicht? (user,task) # check_access_to_cat_module(cat_module) #(user,cat_module) # check_access_to_rpc_intern() # (user) my $subcmd = ( exists $param_hr->{subcmd} ) ? $param_hr->{subcmd} : $EMPTY_STRING; $logger->debug("subcmd [$subcmd]"); my $cmdres_r = {}; # rpc_info | rpc_intern my $rpcmode = ( exists $param_hr->{rpcmode} ) ? $param_hr->{rpcmode} : $EMPTY_STRING; $logger->debug("rpcmode [$rpcmode]"); # task | rpc_intern | cat_module my $entity = ( exists $param_hr->{entity} ) ? $param_hr->{entity} : $EMPTY_STRING; $logger->debug("entity [$entity]"); # single | manifold my $scope = ( exists $param_hr->{scope} ) ? $param_hr->{scope} : $EMPTY_STRING; $logger->debug("scope [$scope]"); my $status = 'FALSE'; # -------------------- check_access_to_task ------------------ if ( $subcmd eq 'task_access' or $subcmd eq 'task_access_survey' ) { $cmdres_r = $self->evaluate_access( { realm => 'task', login => $login, param_hr => $param_hr } ); $status = 'TRUE'; } # -------------------- check_access_to_cat_module ------------ if ( $subcmd eq 'cat_module_access' or $subcmd eq 'cat_module_access_survey' ) { $cmdres_r = $self->evaluate_access( { realm => 'cat_module', login => $login, param_hr => $param_hr } ); $status = 'TRUE'; } # -------------------- check_access_to_rpc_intern ------------ if ( $subcmd eq 'rpc_intern_access' or $subcmd eq 'rpc_intern_access_survey' ) { $cmdres_r = $self->evaluate_access( { realm => 'rpc_intern', login => $login, param_hr => $param_hr } ); $status = 'TRUE'; } # answer my $answer_hr = { header_hr => $HEADER_HREF, cmd => 'rpc_info', login => $login, ticket => $ticket, status => $status, msg => $EMPTY_STRING, type => 'href', cmdres_r => $cmdres_r, ltarget => 'NULL', }; # +------------------------------------------------------------------+ # | API return $answer_hr; } # +----------------------------------------------------------------------+ # | rpc_intern | # +----------------------------------------------------------------------+ sub rpc_intern : PRIVATE { # +------------------------------------------------------------------+ # | API my ( $self, $arg_r ) = @_; my $header_hr = exists $arg_r->{header_hr} ? $self->h( $arg_r->{header_hr} ) : $self->perr('header_hr'); my $login = exists $arg_r->{login} ? $self->l( $arg_r->{login} ) : $self->perr('login'); my $ticket = exists $arg_r->{ticket} ? $self->l( $arg_r->{ticket} ) : $self->perr('ticket'); my $param_hr = exists $arg_r->{param_hr} ? $self->h( $arg_r->{param_hr} ) : $self->perr('param_hr'); # +------------------------------------------------------------------+ # | main my $logger = get_logger(__PACKAGE__); $logger->debug("> login: $login"); $logger->debug( '> ticket: ', $ticket ); $logger->debug('> header_hr: (not printed)'); $logger->debug( '> param_hr', { filter => \&Dumper, value => $param_hr } ); my $status = 'FALSE'; my $subcmd = ( exists $param_hr->{subcmd} ) ? $self->l( $param_hr->{subcmd} ) : $EMPTY_STRING; $logger->debug("subcmd [$subcmd]"); my $cmdres_r = {}; if ( $subcmd eq 'flush' ) { $cmdres_r = { refresh => 1, }; $status = 'TRUE'; $logger->debug('flush cache triggered'); #$self->rbac_graph_cache( { force => 1 } ); $rbac->flush(); $status = 'TRUE'; } if ( $subcmd eq 'cat_module_cache_size' ) { $logger->debug('cat_module_cache_size triggered'); my $size = $rbac->cat_module_cache_size(); $logger->debug("cat_module cache size [$size]"); $cmdres_r = { cat_module_cache_size => $size, }; $status = 'TRUE'; } if ( $subcmd eq 'rpc_intern_cache_size' ) { $logger->debug('rpc_intern_cache_size triggered'); my $size = $rbac->rpc_intern_cache_size(); $logger->debug("rpc_intern cache size [$size]"); $cmdres_r = { rpc_intern_cache_size => $size, }; $status = 'TRUE'; } if ( $subcmd eq 'task_cache_size' ) { $logger->debug('task_cache_size triggered'); my $size = $rbac->task_cache_size(); $logger->debug("task cache size [$size]"); $cmdres_r = { task_cache_size => $size, }; $status = 'TRUE'; } # ----------------- evaluate_access for task --------------- if ( $subcmd eq 'user_task_access' or $subcmd eq 'user_task_access_survey' ) { $cmdres_r = $self->evaluate_access( { realm => 'task', login => $login, param_hr => $param_hr } ); $status = 'TRUE'; } # ----------------- evaluate_access for cat_module --------- if ( $subcmd eq 'user_cat_module_access' or $subcmd eq 'user_cat_module_access_survey' ) { $cmdres_r = $self->evaluate_access( { realm => 'cat_module', login => $login, param_hr => $param_hr } ); $status = 'TRUE'; } # ----------------- evaluate_access for rpc_intern --------- if ( $subcmd eq 'user_rpc_intern_access' or $subcmd eq 'user_rpc_intern_access_survey' ) { $cmdres_r = $self->evaluate_access( { realm => 'rpc_intern', login => $login, param_hr => $param_hr } ); $status = 'TRUE'; } # answer my $answer_hr = { header_hr => $HEADER_HREF, cmd => 'rpc_intern', login => $login, ticket => $ticket, status => $status, msg => $EMPTY_STRING, type => 'href', cmdres_r => $cmdres_r, ltarget => 'NULL', }; # +------------------------------------------------------------------+ # | API return $answer_hr; } } # END INSIDE-OUT CLASS 1; __END__ =pod =for stopwords RPC CipUX rpc Destructor login subcmd syslog logout ttl TTL RBAC uid TODO cipux ini webpage Kuelker log2syslog cipux-rpc.ini CipUX::RPC::Server XML-RPC =head1 NAME CipUX::RPC::Server - RPC server class for CipUX =head1 VERSION version 3.4.0.9 =head1 SYNOPSIS use CipUX::RPC::Server; =head1 DESCRIPTION Provides the functions for CipUX RPC server. =head1 ABSTRACT The CipUX rpc server is a generic abstract class, which can be used by other classes or scripts. =head1 SUBROUTINES/METHODS The following functions are implemented or supported by CipUX::RPC::Server. =head2 BUILD Constructor, see new. =head2 DEMOLISH Destructor. =head2 new Constructor B my $cipux_rpc = CipUX::RPC::Server->new(); =head2 check_authentication Check weather the login has access or not. =head2 is_ticket_bad Return 1 if a ticket is OK otherwise 0; =head2 signal_handler Install signal_handler alias time to die. B $server->signal_handler({}); =head2 answer_requests Answer Requests. B $server->answer_requests({ port=>8000, address=>'localhost', reuse=>0, proto=>'tcp', meth_hr=>TODO task_hr=>TODO }); =head2 check_access_to_task Check the access for login to a task. =head2 check_access_to_rpc_intern Check the access for login to the rpc_intern section. =head2 check_access_to_cat_module Check the access for login to a CAT module. =head2 error Construct an error message. =head2 evaluate_access Evaluate the access to login or a given user depending on the sub command (subcmd) of rpc_info or rpc_intern for one or more realms. Known realms are: task, cat_module, rpc_intern. =head2 update_task Check and update tasks entries. =head2 update_cat_module Check and update CAT modules entries. =head2 log2syslog Log a given message to syslog. =head2 get_config Return the value for a given configuration variable. =head2 rpc_list_functions List the rpc functions. =head2 rpc_start Start the RPC server. B $server->rpc_start({}); =head2 ping The function 'ping' is for testing the connection. It requires not to log in and no arguments. It returns 'OK'. =head2 version Return the CipUX version. =head2 sum The function 'sum' is for testing the connection. It requires not to log in and 2 arguments. It returns the sum of the arguments as a hash reference with a single line. =head2 login Perform a login. =head2 logout Perform a logout. =head2 session Check the ticket and if it is valid update and return a new ticket. =head2 ttl Return the Time To Live. Default 900 seconds. =head2 rpc_task Execute a CipUX::Task. =head2 rpc_info Execute a rpc_info sub-command. =head3 task_access Needs parameter: TASK =head3 task_access_survey Needs parameter: TASK [TASK] ... =head3 cat_module_access Needs parameter: MODULE =head3 cat_module_access_survey Needs parameter: MODULE [MODULE] ... =head3 rpc_intern_access Needs parameter: COMMAND =head3 rpc_intern_access_survey Needs parameter: COMMAND [COMMAND] ... =head2 rpc_intern Execute a rpc_intern sub-command. =head3 ttl Prints current TTL in seconds =head3 cat_module_cache_size Prints current cat module cache size =head3 rpc_intern_cache_size Prints current rpc intern cache size =head3 task_cache_size Prints current task_cache_size of cache =head3 user_task_access Needs parameter: USER TASK =head3 user_task_access_survey Needs parameter: $USER TASK [TASK] ... =head3 user_cat_module_access Needs parameter: USER MODULE =head3 user_cat_module_access_survey Needs parameter: USER MODULE [MODULE] ... =head3 user_rpc_intern_access Needs parameter: USER COMMAND =head3 user_rpc_intern_access_survey Needs parameter: USER COMMAND [COMMAND] ... =head3 flush_cache Flush RPC server RBAC cache =head1 Public XML-RPC functions. All the following CipUX::Task methods are public. Public means that they could be executed remotely. Public means not that everybody can do this remotely. There are two kinds of public functions: (1) Functions without authorization - login - ping - sum (2) Every other function is available only after using 'login' function, with a uid as first parameter and a valid ticket as second parameter. Examples (pseudo code): - (reference to user list) = cipux_task_list_users( uid, ticket ); - (true|false) = logout( uid, ticket ); - (new ticket|false) = session( uid, ticket ); If the uid do not match, or the uid has not the authorization to use the function, or the group of the uid has not the authorization to use the function, or the ticket is expired, or the ticket is not valid the request will not be fulfilled. In other words: if the uid match and has the right and the role also has the right and the ticket is valid and is not expired, the request will be executed. To see real examples have a look at CipUX::RPC::Client client. =head1 DIAGNOSTICS TODO =head1 CONFIGURATION AND ENVIRONMENT Need no environment variables. But do need a configuration file. For example cipux-rpc.ini =head1 DEPENDENCIES Authen::Simple::PAM Authen::Simple::Password Carp CipUX::Task CipUX::RBAC::Simple Class::Std Data::Dumper English Frontier::Daemon Frontier::RPC2 List::MoreUtils Log::Log4perl Log::Dispatch POSIX Readonly Ticket::Simple =head1 INCOMPATIBILITIES Not known. =head1 BUGS AND LIMITATIONS Not known. =head1 SEE ALSO See the CipUX webpage and the manual at L See the mailing list L =head1 AUTHOR Christian Kuelker Echristian.kuelker@cipworx.orgE =head1 LICENSE AND COPYRIGHT Copyright (C) 2007 - 2010 by Christian Kuelker This program 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, or (at your option) any later version. This program 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 this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA =cut CipUX-RPC-3.4.0.9/lib/CipUX/RPC/Server000755001750001750 011432067074 17530 5ustar00ckuelkerckuelker000000000000CipUX-RPC-3.4.0.9/lib/CipUX/RPC/Server/Daemon.pm000444001750001750 3171511432067074 21455 0ustar00ckuelkerckuelker000000000000#!/usr/bin/perl -w -T # +=======================================================================+ # || CipUX::RPC::Server::Daemon || # || || # || Command line interface for CipUX RPC Server. || # || || # || Copyright (C) 2007 - 2008 by Christian Kuelker || # || || # || License: GNU GPL - GNU General Public License - version 2 || # || or (at your opinion) any later version. || # || || # +=======================================================================+ # ID: $Id$ # Revision: $Revision$ # Head URL: $HeadURL$ # Date: $Date$ # Source: $Source package CipUX::RPC::Server::Daemon; use warnings; use strict; use Carp; use Class::Std; use Data::Dumper; use English qw( -no_match_vars); use Getopt::Long; use Log::Log4perl qw(get_logger :levels); use Pod::Usage; use Readonly; use base qw(CipUX::RPC::Server); { # BEGIN INSIDE-OUT CLASS use version; our $VERSION = qv('3.4.0.9'); use re 'taint'; # Keep data captured by parens tainted delete @ENV{qw(PATH IFS CDPATH ENV BASH_ENV)}; # Make %ENV safer # +======================================================================+ # || CONSTANTS || # +======================================================================+ Readonly::Scalar my $SCRIPT => 'CipUX::RPC::Server::Daemon'; Readonly::Scalar my $EMPTY_STRING => q{}; Readonly::Array my @ACTION => qw(cipux_rpcd cipux_rpc_list); Readonly::Scalar my $LINEWIDTH => 78; # +======================================================================+ # || INIT ARGS || # +======================================================================+ my %name_of : ATTR( init_arg => 'name'); # +=======================================================================+ # || GLOBALS || # +=======================================================================+ my $L = q{=} x $LINEWIDTH; $L .= "\n"; my %opt = (); my $script = $EMPTY_STRING; my %option = ( 'cipux_rpcd' => { 'must' => [], 'may' => [ qw(c cfg h ? help D debug t test v verbose version)], 'not' => [qw(l list)], }, 'cipux_rpc_list' => { 'must' => [], 'may' => [qw(c cfg h ? help D debug l list v verbose version)], 'not' => [qw(t test)], }, ); sub run { # +------------------------------------------------------------------+ # | API my ( $self, $arg_r ) = @_; # Constructor param from CipUX::RPC::Server::Daemon my $run_action = $name_of{ ident $self}; # test right away if we have a valid action # is $run_action inside @action? if ( scalar( grep { $_ eq $run_action } @ACTION ) < 1 ) { $self->exc( { msg => 'unknown action', value => $run_action } ); } # +==================================================================+ # || ENVIRONMENT || # +==================================================================+ # %opt have to be empty! # If you have to test of existence, be aware to test it # with "exists" or "defined" of the SINGLE letter version. # Example: exists($opt{D}) might be true if --debug or -D is set, # but exists($opt{debug}) is always (!) false! Getopt::Long::Configure("bundling"); GetOptions( \%opt, "c|cfg=s", "D|debug:s", "h|?|help", "l|list", "p|pretty", "t|task=s", "V|version", "verbose", ) or pod2usage( -exitstatus => 2, -msg => "$L Problems parsing command line!\n$L" ); if ( exists $opt{D} and defined $opt{D} ) { Log::Log4perl::init_and_watch( '/etc/cipux/log4perl.conf', 60 ); } my $logger = get_logger('__PACKAGE__'); $logger->debug('BEGIN'); $logger->debug( ' action: ', $run_action ); # display help page if ( exists( $opt{help} ) or exists( $opt{h} ) ) { pod2usage( -exitstatus => 0, -verbose => 1 ); } # display version an exit if ( exists( $opt{version} ) ) { print "$SCRIPT $VERSION\n"; exit(0); } my $ret = $self->test_cli_option( { script => $run_action, logic_hr => \%option, opt_hr => \%opt, debug => 0, } ); # +==================================================================+ # || MAIN || # +==================================================================+ my $action = exists $opt{action} ? $opt{action} : exists $opt{a} ? $opt{a} : undef; my $cfg = exists $opt{cfg} ? $opt{cfg} : exists $opt{c} ? $opt{c} : undef; # We DO need a configuration #my $msg = # $L . 'EXCEPTION: The default configuration file can not be found. '; #$msg .= 'Please provide --cfg ' . "\n$L"; #pod2usage( -msg => $msg, -exitstatus => 4, -verbose => 0 ) if not $cfg; my $pretty = exists $opt{pretty} ? 1 : exists $opt{p} ? 1 : 0; if ( defined $cfg ) { $logger->debug( ' cfg: ', $cfg ); } $action = $run_action; #$action =~ s{^.*_}{}gmx; $self->$run_action( { action => $action, pretty => $pretty, cfg => $cfg, } ); $logger->debug('END'); # +------------------------------------------------------------------+ # | API return; } # +----------------------------------------------------------------------+ # | cipux_rpcd | # +----------------------------------------------------------------------+ sub cipux_rpcd { # +------------------------------------------------------------------+ # | API my ( $self, $arg_r ) = @_; my $action = exists $arg_r->{action} ? $self->l( $arg_r->{action} ) : $self->perr('action'); # +------------------------------------------------------------------+ # | main my $logger = get_logger(__PACKAGE__); $logger->debug('BEGIN'); $logger->debug( '> action: ', $action ); #$logger->debug( ' cfg: ', { filter => \&Dumper, value => $cfg } ); my $run_action = $action; if ( scalar( grep { $_ eq $run_action } @ACTION ) < 1 ) { $self->exc( { msg => 'unknown action', value => $run_action } ); } my $server = CipUX::RPC::Server->new(); $logger->debug('call server start'); #local $PROGRAM_NAME = "$SCRIPT [accepting connections]"; my $state = $server->rpc_start(); $logger->debug( 'server exited with state: ', $state ); $logger->debug('call server stop'); $server->rpc_stop; $logger->debug('END'); # +------------------------------------------------------------------+ # | API return $state; } # +----------------------------------------------------------------------+ # | cipux_rpc_list | # +----------------------------------------------------------------------+ sub cipux_rpc_list { # +------------------------------------------------------------------+ # | API my ( $self, $arg_r ) = @_; my $cfg = exists $arg_r->{cfg} ? $self->l( $arg_r->{cfg} ) : $self->perr('cfg'); my $pretty = exists $arg_r->{pretty} ? $self->l( $arg_r->{pretty} ) : $self->perr('pretty'); # +------------------------------------------------------------------+ # | main my $logger = get_logger(__PACKAGE__); $logger->debug('BEGIN'); $logger->debug( '> pretty: ', $pretty ); $logger->debug( '> cfg: ', { filter => \&Dumper, value => $cfg } ); my $server = CipUX::RPC::Server->new(); my @list = $server->rpc_list_functions( { scope => 'all' } ); # mostly this stuff is for pretty print my $max_col = 0; my $width = 0; if ($pretty) { foreach my $line (@list) { if ( $max_col < length $line ) { $max_col = length $line; } } $width = 2 + $max_col; $self->out( q{+} . q{-} x $width . "+\n" ); $self->out( sprintf '| %-' . $max_col . "s |\n", 'xml rpc command' ); $self->out( q{+} . q{=} x $width . "+\n" ); } ## end if($pretty) foreach my $line (@list) { chomp $line; if ($pretty) { $self->out( sprintf '| %-' . $max_col . "s |\n", $line ); } else { $self->out("$line\n"); } } ## end foreach my $line (@$list_aref) if ($pretty) { $self->out( q{+} . q{-} x $width . "+\n" ); } $logger->debug('END'); # +------------------------------------------------------------------+ # | API exit 0; #return; } } # END INSIDE-OUT CLASS # Module implementation here 1; # Magic true value required at end of module __END__ =head1 NAME CipUX::RPC::Server::Daemon - Command line interface for CipUX::RPC::Server =head1 VERSION This document describes CipUX::RPC::Server::Daemon version 3.4.0.9 =head1 SYNOPSIS use CipUX::RPC::Server::Daemon; my $daemon = CipUX::RPC::Server::Daemon->new( { name=>'cipux_rpcd' } ); $daemon->run(); =head1 DESCRIPTION Command line interface to CipUX::RPC::Server. =head1 METHODS =head2 cipux_rpcd Start the XML-RPC server. =head2 cipux_rpc_list List CipUX XML-RPC functions. =head2 run Main routine. =head1 DIAGNOSTICS Every single error and warning message that the module can generate (even the ones that will "never happen"), with a full explanation of each problem, one or more likely causes, and any suggested remedies. =over =item C<< Problems parsing command line! >> If Getopt::Long has a non specified problem. See Getopt::Long for details. Most probable reason is: you provide wrong command line switches or values. =back =head1 CONFIGURATION AND ENVIRONMENT CipUX::RPC::Server::Daemon requires no configuration files or environment variables. =head1 DEPENDENCIES Carp CipUX::RPC::Server Class::Std Data::Dumper English Getopt::Long Log::Log4perl Pod::Usage Readonly =head1 INCOMPATIBILITIES None reported. =head1 BUGS AND LIMITATIONS No bugs have been reported. =head1 AUTHOR Christian Kuelker C<< >> =head1 LICENCE AND COPYRIGHT Copyright (C) 2007 - 2008 by Christian Kuelker This module is free software; you can redistribute it and/or modify it under the same terms as Perl itself. See L. =head1 DISCLAIMER OF WARRANTY BECAUSE THIS SOFTWARE IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE SOFTWARE, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE SOFTWARE "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE SOFTWARE IS WITH YOU. SHOULD THE SOFTWARE PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR, OR CORRECTION. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE SOFTWARE AS PERMITTED BY THE ABOVE LICENCE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE SOFTWARE (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE SOFTWARE TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. CipUX-RPC-3.4.0.9/lib/CipUX/RPC/Test000755001750001750 011432067074 17201 5ustar00ckuelkerckuelker000000000000CipUX-RPC-3.4.0.9/lib/CipUX/RPC/Test/Client.pm000444001750001750 17502711432067074 21166 0ustar00ckuelkerckuelker000000000000# +==========================================================================+ # || CipUX::RPC::Test::Client || # || || # || Copyright (C) 2008 - 2010 by Christian Kuelker. All rights reserved! || # || || # || License: GNU GPL - GNU General Public License - version 2 || # || or (at your opinion) any later version. || # +==========================================================================+ # ID: $Id$ # Revision: $Revision$ # Head URL: $HeadURL$ # Date: $Date$ # Source: $Source$ package CipUX::RPC::Test::Client; use 5.008001; use strict; use warnings; use Carp; use Class::Std; use base qw(CipUX::RPC); use CipUX::Task; use Data::Dumper; use Date::Manip; use English qw( -no_match_vars); use Frontier::Client; use Getopt::Long; use Log::Log4perl qw(get_logger :levels); use Pod::Usage; use Readonly; { use version; our $VERSION = qv('3.4.0.9'); use re 'taint'; # Keep data captured by parens tainted delete @ENV{qw(PATH IFS CDPATH ENV BASH_ENV)}; # Make %ENV safe # CONSTANTS Readonly::Scalar my $SCRIPT => 'CipUX::RPC::Test::Client'; Readonly::Scalar my $EMPTY_STRING => q{}; Readonly::Scalar my $LINEWIDTH => 78; Readonly::Scalar my $W => 98; # line width of test. Dep. on task names. Readonly::Scalar my $BORDER => 4; Readonly::Scalar my $BORDER_DIFF_LEFT => 28; Readonly::Scalar my $BORDER_DIFF_RIGHT => 38; Readonly::Scalar my $A_LITTLE_WHILE => 3; # sec of sleep Readonly::Scalar my $ENTITY_OBJ_HR => { admin_account => 'rpctestadmin', assistant_account => 'rpctestassistant', lecturer_account => 'rpctestlecturer', professor_account => 'rpctestprofessor', pupil_account => 'rpctestpupil', student_account => 'rpcteststudent', teacher_account => 'rpctestteacher', tutor_account => 'rpctesttutor', class_share => 'rpctestclass', course_share => 'rpctestcourse', lecture_share => 'rpctestlecture', reading_share => 'rpctestreading', seminar_share => 'rpctestseminar', studygroup_share => 'rpcteststudygroup', team_share => 'rpctestteam', tutorial_share => 'rpctesttutorial', workshop_share => 'rpctestworkshop', }; Readonly::Scalar my $PLAIN_ENTITY_HR => { netgroup => { entity => 'netgroup', entity_obj => 'mytestnetgroup', member => 'client', member_obj => '(testhost1,,)', }, }; Readonly::Scalar my $TEST_COUNT_START => '0000'; Readonly::Scalar my $TEST_COUNT_INC => '0001'; Readonly::Scalar my $TEST_MAX => '9999'; Readonly::Scalar my $TEST_SUM_LEFT => 3; Readonly::Scalar my $TEST_SUM_RIGHT => 4; Readonly::Scalar my $TEST_SUM_RESULT => 7; Readonly::Array my @ACTION => qw(cipux_rpc_test_client); Readonly::Scalar my $L4P_CONF => ( -e '/etc/cipux/log4perl.conf' ) ? '/etc/cipux/log4perl.conf' : $EMPTY_STRING; # OBJECT # register client with name: cipux_rpc_test_client my %name_of : ATTR( init_arg => 'name'); # GLOBAL my $L = q{=} x $LINEWIDTH; $L .= "\n"; my $url = $EMPTY_STRING; my $host = 'localhost'; my $port = 8001; my $ssl = 0; my $ttl = 0; my $config_hr = {}; my $task_hr = {}; my %opt = (); my $destroy = 1; my $login = $EMPTY_STRING; my $ticket = $EMPTY_STRING; my $password = $EMPTY_STRING; my %test = (); my $test = $TEST_COUNT_START; my %title = (); my $testcount = $TEST_COUNT_INC; my $maxtest = $TEST_MAX; my $success_summary = '0'; my $failure_summary = '0'; my %option = ( 'cipux_rpc_test_client' => { 'must' => [], 'may' => [qw(D debug h ? help host maxtest no-ssl port ssl version)], 'not' => [], }, ); sub run { ## no critic Subroutines::ProhibitExcessComplexity # +------------------------------------------------------------------+ # | API my ( $self, $arg_r ) = @_; # constructor pram from CipUX::Object::Client my $run_action = $name_of{ ident $self}; # test right away if we have a valid action # is $run_action inside @action? if ( scalar( grep { $_ eq $run_action } @ACTION ) < 1 ) { $self->exc( { msg => 'unknown action', value => $run_action } ); } # ENVIRONMENT Getopt::Long::Configure('bundling'); # first thing will be defined: debug,help,version GetOptions( \%opt, 'debug|D', 'help|h|?', 'host=s', 'maxtest=i', 'port=i', 'no-ssl', 'ssl', 'version|V', ) or pod2usage( -exitstatus => 2, -msg => "$L problems parsing command line!\n$L" ); if ( $L4P_CONF eq $EMPTY_STRING ) { $self->exc( { msg => 'log4perl.conf not found' } ); } if ( exists $opt{debug} and defined $opt{debug} ) { Log::Log4perl::init_once($L4P_CONF); print "Writing debug output to cipux-test.log\n"; } my $logger = get_logger(__PACKAGE__); my $date = UnixDate( 'today', '%O' ); $logger->debug(" CipUX : $VERSION "); $logger->debug(" date : $date"); $logger->debug(" action: $run_action"); # display help page if ( exists $opt{help} ) { pod2usage( -exitstatus => 0, -verbose => 0 ); } if ( exists $opt{version} ) { $self->out( "$run_action $VERSION\n", ); exit 0; } my $cipux_task = CipUX::Task->new(); $task_hr = $cipux_task->get_task_api_cfg(); my $cipux_rpc = CipUX::RPC->new(); $config_hr = $cipux_rpc->get_rpc_cfg(); $host = ( exists $opt{host} and defined $opt{host} ) ? $opt{host} : ( exists $config_hr->{xml_rpc_address} and defined $config_hr->{xml_rpc_address} ) ? $config_hr->{xml_rpc_address} : 'localhost'; $port = ( exists $opt{port} and defined $opt{port} ) ? $opt{port} : ( exists $config_hr->{xml_rpc_port} and defined $config_hr->{xml_rpc_port} ) ? $config_hr->{xml_rpc_port} : 8001; $ssl = ( exists $opt{ssl} and defined $opt{ssl} ) ? 1 : ( exists $opt{'no-ssl'} and defined $opt{'no-ssl'} ) ? 0 : ( exists $config_hr->{xml_rpc_ssl} and defined $config_hr->{xml_rpc_ssl} ) ? $config_hr->{xml_rpc_ssl} : 0; $ttl = ( exists $config_hr->{xml_rpc_ttl} and defined $config_hr->{xml_rpc_ttl} ) ? $config_hr->{xml_rpc_ttl} : 600; $logger->debug(" host : $host"); $logger->debug(" port : $port"); $logger->debug(" ssl : $ssl"); $logger->debug(" ttl : $ttl"); # url = 'https://localhost:8000/RPC2'; # url = 'http://localhost:8001/RPC2'; my $proto = ($ssl) ? 'https' : 'http'; $url = $proto . q{://} . $host . q{:} . $port . q{/RPC2}; $logger->debug(" url : $url"); if ( exists $opt{maxtest} ) { $maxtest = $opt{maxtest}; $logger->debug( 'maxtest: ', $maxtest, "\n" ); } my $ret = $self->test_cli_option( { script => $run_action, logic_hr => \%option, opt_hr => \%opt, debug => 0, } ); # MAIN $self->test_ping; $self->test_version(); $self->test_sum1(); $self->test_sum2(); $self->test_login(); $self->test_list_admin(); $self->test_ttl(); $self->test_session(); $self->test_entity1(); $self->test_logout(); $self->test_list_users(); $self->test_login_again(); $self->test_entity2(); # this test is disabled. We do not need to add orga nodes for the # moment under ou=CipUX and we do not have write access on Debian Edu # on the hole subtree dc=skole,dc=skolelinux,dc=no for now. # $self->test_create_list_destroy1(); $self->test_create_list_destroy2(); $self->test_plain_entity(); $self->test_summary(); return; } sub test_ping { my ( $self, $arg_r ) = @_; my $logger = get_logger(__PACKAGE__); my $answer_hr = $self->xmlrpc( { title => 'CipUX::RPC ping exec', url => $url, login => 'dummy', ticket => 'dummy', cmd => 'ping', param_hr => {}, expect => 'TRUE', } ); return; } sub test_version { my ( $self, $arg_r ) = @_; my $logger = get_logger(__PACKAGE__); my $answer_hr = $self->xmlrpc( { title => 'CipUX::RPC version exec', url => $url, login => 'dummy', ticket => 'dummy', cmd => 'version', param_hr => {}, expect => 'TRUE', } ); my $version_hr = $answer_hr->{cmdres_r}; $self->record_test_result( { start => 1, title => 'CipUX::RPC version result', mode => 'true', true => ( $version_hr->{cipux_version} ne $EMPTY_STRING and $version_hr->{rpc_version} ne $EMPTY_STRING and $version_hr->{cipux_version} ne $EMPTY_STRING ), print => ' CipUX version: ' . $version_hr->{cipux_version} . ', ' . ' RPC version: ' . $version_hr->{rpc_version} . ', ' . ' server version: ' . $version_hr->{cipux_version}, success => 'Got version', failure => 'Did not got version' } ); return; } sub test_sum1 { my ( $self, $arg_r ) = @_; my $logger = get_logger(__PACKAGE__); my $answer_hr = $self->xmlrpc( { title => 'CipUX::RPC sum 1 exec', url => $url, login => 'dummy', ticket => 'dummy', cmd => 'sum', param_hr => { summand1 => $TEST_SUM_LEFT, summand2 => $TEST_SUM_RIGHT, }, expect => 'TRUE', } ); $logger->debug( 'answer_hr: ', { filter => \&Dumper, value => $answer_hr } ); my $sum = $answer_hr->{cmdres_r}->[0]; $self->record_test_result( { start => 1, title => 'CipUX::RPC sum 1 result check', mode => 'true', true => ( $sum eq "$TEST_SUM_RESULT" ), success => "the sum function is working. $TEST_SUM_LEFT plus $TEST_SUM_RIGHT equal $TEST_SUM_RESULT.", failure => "the sum function is NOT working. $TEST_SUM_LEFT plus $TEST_SUM_RIGHT not equal $TEST_SUM_RESULT." } ); return; } sub test_sum2 { my ( $self, $arg_r ) = @_; my $logger = get_logger(__PACKAGE__); my $answer_hr = $self->xmlrpc( { title => 'CipUX::Task sum 2 test', url => $url, login => 'dummy', ticket => 'dummy', cmd => 'cipux_task_sum', param_hr => { summand1 => $TEST_SUM_LEFT, summand2 => $TEST_SUM_RIGHT, }, expect => 'TRUE', } ); my $sum2 = $answer_hr->{cmdres_r}->[0]; $self->record_test_result( { start => 1, title => 'CipUX::Task sum 2 result check', mode => 'true', true => ( $sum2 eq "$TEST_SUM_RESULT" ), success => "GOOD: the sum2 function is working. $TEST_SUM_LEFT plus $TEST_SUM_RIGHT equal $TEST_SUM_RESULT", failure => "BAD: the sum2 function is NOT working. $TEST_SUM_LEFT plus $TEST_SUM_RIGHT NOT equal $TEST_SUM_RESULT" } ); return; } sub test_login { my ( $self, $arg_r ) = @_; my $logger = get_logger(__PACKAGE__); #1 $self->manual_test( { title => 'CipUX login prompt exec' } ); $self->out(" Give a valid login (cipadmin) and a password,\n"); $self->out(" to log in to the CipUX XML-RPC server.\n"); $self->out( " ATTENTION: This will test XML-RPC server! It will add\n"); $self->out( " add and DELETE accounts on the LDAP server! You should\n"); $self->out( " NOT do this on a production server, you might lose\n"); $self->out(" data! If you want to chancel press CTRl-C now.\n"); $login = $self->login_prompt( { prompt => ' Login: ' } ); $password = $self->password_prompt( { prompt => ' Password: ' } ); $self->login( { login => $login, password => $password } ); return; } sub test_list_admin { my ( $self, $arg_r ) = @_; my $logger = get_logger(__PACKAGE__); $logger->debug('list admin accounts exec'); my $answer_hr = $self->xmlrpc( { title => 'CipUX::Task list admin accounts exec', url => $url, login => $login, ticket => $ticket, cmd => 'cipux_task_list_admin_accounts', param_hr => {}, expect => 'TRUE', } ); $logger->debug('search member list for rpctestadmin'); #my $result = $self->member_list({answer_hr => $answer_hr, member => 'rpctestadmin'}); my @admin = $self->member_list( { answer_hr => $answer_hr } ); $logger->debug( 'result: ', scalar @admin ); $self->record_test_result( { start => 1, title => 'CipUX::Task list admin accounts result', mode => 'compare', minor => 0, major => scalar @admin, success => 'OK CipUX::Task list admin test', failure => 'BAD CipUX::Task list admin test' } ); return; } sub test_ttl { my ( $self, $arg_r ) = @_; my $logger = get_logger(__PACKAGE__); my $answer_hr = $self->xmlrpc( { title => 'CipUX::RPC ttl exec', url => $url, login => $login, ticket => $ticket, cmd => 'ttl', param_hr => {}, expect => 'TRUE', } ); my $got_ttl = $answer_hr->{cmdres_r}->{ttl} || 0; $self->record_test_result( { start => 1, title => 'CipUX::RPC ttl result', mode => 'compare', minor => 0, major => $got_ttl, print => 'TTL is ' . $got_ttl . ' seconds.', success => 'We got a TTL', failure => 'We got no TTL' } ); $self->record_test_result( { start => 1, title => 'CipUX::RPC ttl value', mode => 'true', true => ( $ttl == $got_ttl ), major => $got_ttl, success => 'Configuration TTL and Server TTL is equal', failure => 'Configuration TTL and Server TTL is NOT equal' } ); return; } sub test_session { my ( $self, $arg_r ) = @_; my $logger = get_logger(__PACKAGE__); sleep $A_LITTLE_WHILE; my $ticket_save = $ticket; my $answer_hr = $self->xmlrpc( { title => 'CipUX::RPC session exec', url => $url, login => $login, ticket => $ticket, cmd => 'session', param_hr => {}, expect => 'TRUE', } ); $self->record_test_result( { start => 1, title => 'CipUX::RPC session result', mode => 'true', true => ( $ticket ne $ticket_save ), success => 'GOOD: we got a different ticket.', failure => 'BAD: tickets are the same.' } ); return; } # cipux_task_{create|list|destroy}_student_account sub test_entity1 { my ( $self, $arg_r ) = @_; my $logger = get_logger(__PACKAGE__); foreach my $entity ( sort keys %{$ENTITY_OBJ_HR} ) { $self->big_entity_test( { entity => $entity, obj => $ENTITY_OBJ_HR->{$entity} } ); } return; } sub test_logout { my ( $self, $arg_r ) = @_; my $logger = get_logger(__PACKAGE__); my $answer_hr = $self->xmlrpc( { title => 'CipUX::Task logout exec', url => $url, login => $login, ticket => $ticket, cmd => 'logout', param_hr => {}, expect => 'TRUE', } ); return; } sub test_list_users { my ( $self, $arg_r ) = @_; my $logger = get_logger(__PACKAGE__); my $answer_hr = $self->xmlrpc( { title => 'CipUX::Task list user accounts exec', url => $url, login => $login, ticket => $ticket, cmd => 'cipux_task_list_user_accounts', param_hr => {}, expect => 'FALSE', } ); if ( defined $answer_hr->{msg} ) { $self->out(" Server answer: $answer_hr->{msg}\n"); } my $result = $self->member_list( { answer_hr => $answer_hr, member => 'cipadmin' } ); $self->record_test_result( { start => 1, title => 'CipUX::Task list user account result', mode => 'compare', minor => $result, major => 1, print => 'cipux_task_list_user_accounts should fail,' . ' because we are logged out.', success => 'ok, got no objects', failure => 'bad, got objects' } ); return; } sub test_login_again { my ( $self, $arg_r ) = @_; my $logger = get_logger(__PACKAGE__); $self->login( { login => $login, password => $password } ); return; } # cipux_task_{create|list|destroy}_course_share sub test_entity2 { my ( $self, $arg_r ) = @_; my $logger = get_logger(__PACKAGE__); $self->big_entity_test( { entity => 'course_share', obj => 'testcourse' } ); return; } # cipux_task_{create|list|destroy}_ldap_orga_node sub test_create_list_destroy1 { my ( $self, $arg_r ) = @_; my $logger = get_logger(__PACKAGE__); $self->create_list_destroy_test( { title => 'CipUX::Task', create => 'cipux_task_create_ldap_orga_node', destroy => 'cipux_task_destroy_ldap_orga_node', list => 'cipux_task_list_ldap_orga_nodes', testobj => 'testorganode', add_hr => {} } ); return; } # cipux_task_{create|list|destroy}_cat_module sub test_create_list_destroy2 { my ( $self, $arg_r ) = @_; my $logger = get_logger(__PACKAGE__); $self->create_list_destroy_test( { title => 'CipUX::Task', create => 'cipux_task_register_cat_module', destroy => 'cipux_task_deregister_cat_module', list => 'cipux_task_list_cat_modules', testobj => 'testcatmodule.cgi', add_hr => { cipuxTemplate => 'simpeladmin', cipuxName => 'Test CAT module', cipuxEntity => 'student_account', cipuxYear => '2008', cipuxModality => 'student', cipuxIcon => 'student.png', cipuxIsModuleArray => 'FALSE', cipuxShortDescription => 'Short Description', cipuxIsEnabled => 'FALSE', } } ); return; } sub test_plain_entity { my ( $self, $arg_r ) = @_; my $logger = get_logger(__PACKAGE__); foreach my $entity ( keys %{$PLAIN_ENTITY_HR} ) { $self->plain_entity_test( { entity_hr => $PLAIN_ENTITY_HR->{$entity} } ); } return; } sub test_summary { my ( $self, $arg_r ) = @_; my $logger = get_logger(__PACKAGE__); $self->out($L); my $width = $W - $BORDER; my $tlength = $W - $BORDER_DIFF_LEFT; my $slength = $W - $BORDER_DIFF_RIGHT; $self->out(" Summary:\n"); $self->out( ' +' . q{-} x ( $width + 1 ) . "+\n" ); foreach my $test ( sort { $a <=> $b } keys %test ) { printf ' | Test %04s: %-' . $tlength . "s %-10s |\n", $test, $title{$test}, $test{$test}; } $self->out( ' +' . q{-} x ( $width + 1 ) . "+\n" ); if ( $success_summary > 0 ) { $success_summary--; } printf ' | ' . q{ } x $slength . "SUCCESS: %-6s FAILURE: %-6s |\n", $success_summary, $failure_summary; $self->out( ' +' . q{-} x ( $width + 1 ) . "+\n" ); return; } # # out("="x76,"\n"); # out("($test) connect to $url over SSL ...\n"); # out(" try to get the userlist with cipux_task_change_own_password\n"); # @args=(); # push(@args,$login); # push(@args,"password"); # $server->call('cipux_task_change_own_password',@args); # exit 0; # out("="x76,"\n"); # out("($test) connect to $url over SSL ...\n"); # out(" try to get the userlist with cipux_task_change_own_password\n"); # @args=(); # push(@args,$login); # push(@args,"password"); # $server->call('cipux_task_change_own_password',@args); # # exit 0; sub xmlrpc { ## no critic Subroutines::ProhibitExcessComplexity # API my ( $self, $arg_r ) = @_; my $logger = get_logger(__PACKAGE__); my $login = exists $arg_r->{login} ? $self->l( $arg_r->{login} ) : $self->perr('login'); $ticket = exists $arg_r->{ticket} ? $self->l( $arg_r->{ticket} ) : $self->perr('ticket'); my $cmd = exists $arg_r->{cmd} ? $self->l( $arg_r->{cmd} ) : $self->perr('cmd'); my $param_hr = exists $arg_r->{param_hr} ? $self->h( $arg_r->{param_hr} ) : $self->perr('param_hr'); my $url = exists $arg_r->{url} ? $self->l( $arg_r->{url} ) : $self->perr('url'); my $title = exists $arg_r->{title} ? $self->l( $arg_r->{title} ) : $self->perr('title'); my $expect = exists $arg_r->{expect} ? $self->l( $arg_r->{expect} ) : $self->perr('expect'); my $print = $self->l( $arg_r->{print} ) || $EMPTY_STRING; #if ( defined $title{$test} ) { # $logger->debug( 'title : ', $title{$test} ); #} $logger->debug( 'test : ', $test ); $logger->debug( 'test nr: ', $testcount ); $logger->debug( 'maxtest: ', $maxtest ); $logger->debug( 'command: ', $cmd ); $logger->debug( 'url : ', $url ); exit 0 if $test >= $maxtest; $test++; $title{$test} = $title; $testcount++; $self->out($L); $self->out("($test) ==> $title{$test} <== (RPC) \n"); $self->out(" command $cmd\n"); $self->out(" connect to $url ...\n"); if ( $print ne $EMPTY_STRING ) { $self->out(" $print\n"); } my $HEADER_HREF = { 'cipux_version' => '3.4.0.0', 'client_name' => 'cipux_rpc_test_client', 'client_version' => '3.4.0.0', 'rpc_version' => '2.0', 'client_key' => $EMPTY_STRING, 'client_cred' => $EMPTY_STRING, 'gmt_time' => time(), }; my $pay_hr = { header_hr => $HEADER_HREF, login => $login, ticket => $ticket, cmd => $cmd, param_hr => $param_hr }; my $cmd_type = $cmd; if ( $cmd =~ m/cipux_task/xms ) { $cmd_type = 'task'; } my $socket = Frontier::Client->new( url => $url ); my $answer_hr = $socket->call( $cmd_type, $pay_hr ); my $status = $answer_hr->{status} || 'UNKNOWN'; $self->out(" the expected status should be: [$expect] \n"); $self->out(" the status of the request was: [$status] \n"); if ( $status eq $expect ) { $self->out(" Test $test ... SUCCESS\n"); $test{$test} = 'SUCCESS'; $success_summary++; $logger->debug( 'status : ', $test{$test} ); } else { $self->out(" Test $test ... FAILURE\n"); $test{$test} = 'FAILURE'; $failure_summary++; $logger->debug( 'status : ', $test{$test} ); if ( defined $answer_hr->{msg} ) { $self->out( ' Message from Server: ' . $answer_hr->{msg} . "\n" ); } } # remember, if we got new ticket on the default channel if ( defined $answer_hr->{ticket} and $answer_hr->{ticket} ne $EMPTY_STRING ) { $ticket = $answer_hr->{ticket}; } # but if we got a ticket explcit: eq. login we should use this if ( ref( $answer_hr->{cmdres_r} ) eq 'HASH' and defined $answer_hr->{cmdres_r}->{ticket} and $answer_hr->{cmdres_r}->{ticket} ne $EMPTY_STRING ) { $ticket = $answer_hr->{cmdres_r}->{ticket}; } return $answer_hr; } sub manual_test { my ( $self, $arg_r ) = @_; my $logger = get_logger(__PACKAGE__); my $title = exists $arg_r->{title} ? $self->l( $arg_r->{title} ) : $self->perr('title'); exit 0 if $test >= $maxtest; $test++; $title{$test} = $title; $testcount++; $self->out($L); $self->out("($test) ==> $title{$test} <== (M) \n"); return; } sub record_test_result { #API my ( $self, $arg_r ) = @_; my $logger = get_logger(__PACKAGE__); my $mode = exists $arg_r->{mode} ? $self->l( $arg_r->{mode} ) : $self->perr('mode'); my $title = exists $arg_r->{title} ? $self->l( $arg_r->{title} ) : $self->perr('title'); my $failure = exists $arg_r->{failure} ? $self->l( $arg_r->{failure} ) : $self->perr('failure'); my $success = exists $arg_r->{success} ? $self->l( $arg_r->{success} ) : $self->perr('success'); my $start = exists $arg_r->{start} ? $self->l( $arg_r->{start} ) : 0; my $print = exists $arg_r->{print} ? $self->l( $arg_r->{print} ) : $EMPTY_STRING; if ($start) { exit 0 if $test >= $maxtest; $test++; $title{$test} = $title; $testcount++; $self->out($L); $self->out("($test) ==> $title{$test} <== (M) \n"); } else { $title{$test} = $title; } # if ($print) { # $self->out( q{ } . $print . "\n" ); # } my $result = 0; if ( $mode eq 'true' ) { if ( $self->l( $arg_r->{true} ) ) { # API2 $self->out(" Test $test SUCCESS\n"); $test{$test} = 'SUCCESS'; $success_summary++; $result = 1; $logger->debug( 'status : ', $test{$test} ); } else { $self->out(" Test $test FAILURE\n"); $test{$test} = 'FAILURE'; $failure_summary++; $logger->debug( 'status : ', $test{$test} ); } } elsif ( $mode eq 'compare' ) { # API2 my $minor = exists $arg_r->{minor} ? $self->l( $arg_r->{minor} ) : 0; my $major = exists $arg_r->{major} ? $self->l( $arg_r->{major} ) : 0; if ( $minor < $major ) { $self->out(" Test $test SUCCESS\n"); $test{$test} = 'SUCCESS'; $success_summary++; $result = 1; $logger->debug( 'status : ', $test{$test} ); } else { $self->out(" Test $test FAILURE\n"); $test{$test} = 'FAILURE'; $failure_summary++; $logger->debug( 'status : ', $test{$test} ); } } if ($result) { $self->out( q{ (} . $success . ")\n" ); } else { $self->out( q{ (} . $failure . ")\n" ); } return $result; } sub login { my ( $self, $arg_r ) = @_; my $logger = get_logger(__PACKAGE__); my $login = exists $arg_r->{login} ? $self->l( $arg_r->{login} ) : $self->perr('login'); my $password = exists $arg_r->{password} ? $self->l( $arg_r->{password} ) : $self->perr('password'); my $result = $self->record_test_result( { start => 0, title => 'CipUX login prompt exec', mode => 'true', true => ( $login eq 'cipadmin' and defined $password and $password ne $EMPTY_STRING ), success => 'login prompt works', failure => 'We will not continue. We stop the test now.' . 'Please provide login - cipadmin - and password next time.' } ); exit 1 if not $result; #2 my $answer_hr = $self->xmlrpc( { title => 'CipUX::RPC login test exec', url => $url, login => $login, ticket => 'dummy', cmd => 'login', param_hr => { password => $password, }, expect => 'TRUE', } ); #3 $result = $self->record_test_result( { start => 1, title => 'CipUX::RPC login result', mode => 'true', true => ( $answer_hr->{status} eq 'TRUE' ), success => 'GOOD, we are in.', failure => 'BAD, we are not in. We will not continue.' . ' We stop the test now.' . ' Please provide valid login and password next time.' } ); exit 1 if not $result; #$ticket = $answer_hr->{cmdres_r}->{ticket}; #4 $result = $self->record_test_result( { start => 1, title => 'CipUX::RPC login ticket result', mode => 'true', true => ( defined $ticket and $ticket ne $EMPTY_STRING ), success => 'OK, got secret ticket. You should not print that.', failure => 'BAD: wrong login or password' } ); exit 1 if not $result; return; } # cipux_task_{create|list|destroy}_{$entity} # cipux_task_create_{entity} # cipux_task_change_{entity}_password # cipux_task_add_member_to_{entity} # cipux_task_list_members_of_{entity} # cipux_task_remove_member_from_{entity} # cipux_task_list_{entity}s # cipux_task_destroy_{entity} # sub big_entity_test { my ( $self, $arg_r ) = @_; my $logger = get_logger(__PACKAGE__); my $entity = exists $arg_r->{entity} ? $self->l( $arg_r->{entity} ) : $self->perr('entity'); my $obj = ( exists $arg_r->{obj} and defined $arg_r->{obj} ) ? $self->l( $arg_r->{obj} ) : $self->perr('obj'); $logger->debug("probably delete $obj of $entity"); $self->probably_delete( { title => 'CipUX::Task 01 maybe destroy ' . $entity, list => 'cipux_task_list_' . $entity . 's', destroy => 'cipux_task_destroy_' . $entity, testobj => $obj, } ); # cipux_task_create_{ $entity } $logger->debug( 'RPC CREATE: cipux_task_create_' . $entity ); my $answer_hr = $self->xmlrpc( { title => 'CipUX::Task 03 create ' . $entity . ' exec', print => 'operating on: ' . $obj, url => $url, login => $login, ticket => $ticket, cmd => 'cipux_task_create_' . $entity . $EMPTY_STRING, param_hr => { object => $obj, cipuxFirstname => 'Test', cipuxLastname => 'Object', }, expect => 'TRUE', } ); $logger->debug( 'RPC CREATE: cipux_task_list_' . $entity . 's' ); $answer_hr = $self->xmlrpc( { title => 'CipUX::Task 04 create ' . $entity . ' list', print => 'operating on: ' . $obj, url => $url, login => $login, ticket => $ticket, cmd => 'cipux_task_list_' . $entity . 's', param_hr => {}, expect => 'TRUE', } ); $logger->debug('test if account has been created'); my $result = $self->member_list( { answer_hr => $answer_hr, member => $obj } ); $logger->debug("result: $result"); my $test_account_created = $result; $logger->debug('Record CREATE: recording the result'); $self->record_test_result( { start => 1, title => 'CipUX::Task 05 create ' . $entity . ' result', print => 'operating on: ' . $obj, mode => 'true', true => ($test_account_created), print => 'test if it was created ...', success => 'test account created.', failure => 'Test account not created.' } ); # cipux_task_change_{. $entity . }_password $logger->debug( 'RPC CHANGE PWD: change_' . $entity . '_password' ); my $rnd_password = $self->random_password(); $answer_hr = $self->xmlrpc( { title => 'CipUX::Task 07 change password of ' . $entity . ' exec', print => 'operating on: ' . $obj, url => $url, login => $login, ticket => $ticket, cmd => 'cipux_task_change_' . $entity . '_password', #param_hr => { object => $obj, userPassword => $rnd_password }, param_hr => { object => $obj, value => $rnd_password }, expect => 'TRUE', } ); # cipux_task_add_member_{ $entity } # cipux_task_add_member_to_course_share # cipux_task_list_members_of_course_share # cipux_task_remove_member_from_course_share my $member = 'testmember'; $answer_hr = $self->xmlrpc( { title => "CipUX::Task 08 add $member to $entity exec", print => 'operating on: ' . $obj . ' and ' . $member, url => $url, login => $login, ticket => $ticket, cmd => 'cipux_task_add_member_to_' . $entity . $EMPTY_STRING, #param_hr => { object => $obj, memberUid => $member }, param_hr => { object => $obj, value => $member }, expect => 'TRUE', } ); $answer_hr = $self->xmlrpc( { title => "CipUX::Task 09 add $member to $entity list", url => $url, login => $login, ticket => $ticket, cmd => 'cipux_task_list_members_of_' . $entity . $EMPTY_STRING, param_hr => { object => $obj }, expect => 'TRUE', } ); $logger->debug( 'answer_hr: ', { filter => \&Dumper, value => $answer_hr } ); $logger->debug( 'obj : ', $obj, "\n" ); $result = $self->member_list( { answer_hr => $answer_hr, member => $member } ); $self->record_test_result( { start => 1, title => 'CipUX::Task 10 add ' . $member . ' to ' . $entity . ' result', mode => 'true', true => $result, print => 'test if it was created ... ' . $member, success => 'GOOD: test member ' . $member . ' is there', failure => 'BAD: ' . $member . ' not there.' } ); $answer_hr = $self->xmlrpc( { title => "CipUX::Task 11 remove $member from $entity remove", url => $url, login => $login, ticket => $ticket, cmd => 'cipux_task_remove_member_from_' . $entity . $EMPTY_STRING, param_hr => { object => $obj, value => $member }, expect => 'TRUE', } ); $answer_hr = $self->xmlrpc( { title => "CipUX::Task 12 remove $member from $entity list", url => $url, login => $login, ticket => $ticket, cmd => 'cipux_task_list_members_of_' . $entity . $EMPTY_STRING, param_hr => { object => $obj }, expect => 'TRUE', } ); $result = $self->member_list( { answer_hr => $answer_hr, member => $member } ); $self->record_test_result( { start => 1, title => 'CipUX::Task 13 add member to ' . $entity . ' result', mode => 'true', true => ( not $result ), print => 'test if it was created ... ' . $member, success => 'GOOD: test member ' . $member . ' is not there', failure => 'BAD: ' . $member . ' there.' } ); # cipux_task_destroy_{ $entity } if ($destroy) { my $answer_hr = $self->xmlrpc( { title => 'CipUX::Task 14 destroy ' . $entity . ' exec', url => $url, login => $login, ticket => $ticket, cmd => 'cipux_task_destroy_' . $entity . $EMPTY_STRING, param_hr => { object => $obj, }, expect => 'TRUE', } ); } $answer_hr = $self->xmlrpc( { title => 'CipUX::Task 15 destroy ' . $entity . ' list', url => $url, login => $login, ticket => $ticket, cmd => 'cipux_task_list_' . $entity . 's', param_hr => {}, expect => 'TRUE', } ); $result = $self->member_list( { answer_hr => $answer_hr, member => $obj } ); $self->record_test_result( { start => 1, title => 'CipUX::Task 16 destroy ' . $entity . ' result', mode => 'true', true => ( not $result ), print => 'test if it was created ...', success => 'test account deleted.', failure => 'Test account not deleted.' } ); return; } # plain_entity_test # cipux_task_create_{entity} # cipux_task_list_{entity}s # cipux_task_addmodify_all_clients_of_netgroup (add) # cipux_task_list_{target}s_of_{entity} # cipux_task_erase_clients_from_netgroup # cipux_task_list_{target}s_of_{entity} # cipux_task_destroy_{entity} # sub plain_entity_test { my ( $self, $arg_r ) = @_; my $logger = get_logger(__PACKAGE__); my $e_hr = exists $arg_r->{entity_hr} ? $self->h( $arg_r->{entity_hr} ) : $self->perr('entity_hr'); my $entity = exists $e_hr->{entity} ? $self->l( $e_hr->{entity} ) : $self->perr('e_hr->{entity}'); my $entity_obj = exists $e_hr->{entity_obj} ? $self->l( $e_hr->{entity_obj} ) : $self->perr('e_hr->{entity_obj}'); my $member = exists $e_hr->{member} ? $self->l( $e_hr->{member} ) : $self->perr('e_hr->{member}'); my $member_obj = exists $e_hr->{member_obj} ? $self->l( $e_hr->{member_obj} ) : $self->perr('e_hr->{member_obj}'); $logger->debug( "probably plain delete $entity_obj of $entity", "\n" ); $self->probably_delete( { title => 'CipUX::Task 01 maybe plain destroy ' . $entity, list => 'cipux_task_list_' . $entity . 's', destroy => 'cipux_task_destroy_' . $entity, testobj => $entity_obj, } ); # cipux_task_create_{ $entity } my $answer_hr = $self->xmlrpc( { title => 'CipUX::Task 03 create ' . $entity . ' exec', print => 'operating on: ' . $entity_obj, url => $url, login => $login, ticket => $ticket, cmd => 'cipux_task_create_' . $entity . $EMPTY_STRING, param_hr => { object => $entity_obj, }, expect => 'TRUE', } ); $answer_hr = $self->xmlrpc( { title => 'CipUX::Task 04 create ' . $entity . ' list', print => 'operating on: ' . $entity_obj, url => $url, login => $login, ticket => $ticket, cmd => 'cipux_task_list_' . $entity . 's', param_hr => {}, expect => 'TRUE', } ); my $result = $self->member_list( { answer_hr => $answer_hr, member => $entity_obj } ); $self->record_test_result( { start => 1, title => 'CipUX::Task 05 create ' . $entity . ' result', print => 'operating on: ' . $entity_obj, mode => 'true', true => $result, print => 'test if it was created ...', success => 'test account created.', failure => 'Test account not created.' } ); # cipux_task_add_member_{ $entity } # cipux_task_addmodify_all_clients_of_netgroup # cipux_task_list_clients_of_netgroup # cipux_task_erase_clients_from_netgroup #cmd => 'cipux_task_add_'.$member_obj.'_to_' . $entity . $EMPTY_STRING, my $cmd = ( $entity eq 'netgroup' ) ? 'cipux_task_addmodify_all_clients_of_netgroup' : 'cipux_task_change_' . $entity . '_' . $member; $answer_hr = $self->xmlrpc( { title => "CipUX::Task 06 add $member to $entity exec", print => 'operating on: ' . $entity_obj . ' and ' . $member_obj, url => $url, login => $login, ticket => $ticket, cmd => $cmd, #param_hr => { object => $obj, memberUid => $member }, param_hr => { object => $entity_obj, value => $member_obj }, expect => 'TRUE', } ); my $tsk07cmd = "cipux_task_list_$member"; $tsk07cmd .= "s_of_$entity" . $EMPTY_STRING; $answer_hr = $self->xmlrpc( { title => "CipUX::Task 07 add $member to $entity list", url => $url, login => $login, ticket => $ticket, cmd => $tsk07cmd, param_hr => { object => $entity_obj }, expect => 'TRUE', } ); my @member = sort @{ $answer_hr->{cmdres_r}->{$entity_obj}->{nisNetgroupTriple} }; my $memberlist = join q{, }, @member; my %member = (); my $z = 0; foreach my $m (@member) { $member{$m} = 1; $self->out("got member $m\n"); $z++; } $self->record_test_result( { start => 1, title => 'CipUX::Task 08 add member to ' . $entity . ' result', mode => 'true', true => ( defined $member{$member_obj} ), print => 'test if it was created ... ' . $member_obj, success => 'GOOD: test member ' . $member_obj . ' is there', failure => 'BAD: ' . $member_obj . ' not there.' } ); $answer_hr = $self->xmlrpc( { title => "CipUX::Task 09 erase $member 's of $entity list", url => $url, login => $login, ticket => $ticket, cmd => 'cipux_task_erase_' . $member . 's_of_' . $entity . $EMPTY_STRING, #param_hr => { object => $entity_obj, value => $member_obj }, param_hr => { object => $entity_obj, value => $EMPTY_STRING }, expect => 'TRUE', } ); # cipux_task_destroy_{ $entity } if ($destroy) { my $answer_hr = $self->xmlrpc( { title => 'CipUX::Task 10 destroy ' . $entity . ' exec', url => $url, login => $login, ticket => $ticket, cmd => 'cipux_task_destroy_' . $entity . $EMPTY_STRING, param_hr => { object => $entity_obj, }, expect => 'TRUE', } ); } $answer_hr = $self->xmlrpc( { title => 'CipUX::Task 11 destroy ' . $entity . ' list', url => $url, login => $login, ticket => $ticket, cmd => 'cipux_task_list_' . $entity . 's', param_hr => {}, expect => 'TRUE', } ); $result = $self->member_list( { answer_hr => $answer_hr, member => $entity_obj } ); $self->record_test_result( { start => 1, title => 'CipUX::Task 12 destroy ' . $entity . ' result', mode => 'true', true => ( not $result ), print => 'test if it was created ...', success => 'test account deleted.', failure => 'Test account not deleted.' } ); return; } sub probably_delete { my ( $self, $arg_r ) = @_; my $logger = get_logger(__PACKAGE__); my $testobj = exists $arg_r->{testobj} ? $self->l( $arg_r->{testobj} ) : $self->perr('testobj'); my $list = exists $arg_r->{list} ? $self->l( $arg_r->{list} ) : $self->perr('list'); my $destroy = exists $arg_r->{destroy} ? $self->l( $arg_r->{destroy} ) : $self->perr('destroy'); my $title = exists $arg_r->{title} ? $self->l( $arg_r->{title} ) : $self->perr('title'); $logger->debug("testobj: $testobj"); $logger->debug("list : $list"); $logger->debug("destroy: $destroy"); $logger->debug("title : $title"); # cipux_task_destroy_'.$entity.' (maybe) # (1) first list, to see if it is there my $answer_hr = $self->xmlrpc( { title => $title . q{ } . $testobj . ' list', url => $url, login => $login, ticket => $ticket, cmd => $list, param_hr => {}, expect => 'TRUE', } ); $logger->debug( 'answer_hr: ', { filter => \&Dumper, value => $answer_hr } ); # remember status, if we have to destroy the testaccount first my $result = $self->member_list( { answer_hr => $answer_hr, member => $testobj } ); #(2) the, if it is there destroy it if ($result) { if ($destroy) { my $answer_hr = $self->xmlrpc( { title => $title . q{ } . $testobj . ' exec', print => 'operating on: ' . $testobj, url => $url, login => $login, ticket => $ticket, cmd => $destroy, param_hr => { object => $testobj, }, expect => 'TRUE', } ); } } else { $title =~ s/01/02/xms; # this is a present $self->record_test_result( { start => 1, title => $title . q{ } . $testobj . ' exec', print => 'operating on: ' . $testobj, mode => 'true', true => ( 1 == 1 ), success => 'GOOD: This is a present.', failure => 'BAD: Not possible.' } ); } return; } sub create_list_destroy_test { my ( $self, $arg_r ) = @_; my $logger = get_logger(__PACKAGE__); my $title = exists $arg_r->{title} ? $self->l( $arg_r->{title} ) : $self->perr('title'); my $create = exists $arg_r->{create} ? $self->l( $arg_r->{create} ) : $self->perr('create'); my $destroy = exists $arg_r->{destroy} ? $self->l( $arg_r->{destroy} ) : $self->perr('destroy'); my $list = exists $arg_r->{list} ? $self->l( $arg_r->{list} ) : $self->perr('list'); my $testobj = exists $arg_r->{testobj} ? $self->l( $arg_r->{testobj} ) : $self->perr('testobj'); my $add_hr = exists $arg_r->{add_hr} ? $self->h( $arg_r->{add_hr} ) : $self->perr('add_hr'); $self->probably_delete( { title => $title . ' probably delete', list => $list, destroy => $destroy, testobj => $testobj, } ); $add_hr->{object} = $testobj; my $answer_hr = $self->xmlrpc( { title => $title . q{ } . $create . ' exec', url => $url, login => $login, ticket => $ticket, cmd => $create, param_hr => $add_hr, expect => 'TRUE', } ); $answer_hr = $self->xmlrpc( { title => $title . q{ } . $list . ' list', url => $url, login => $login, ticket => $ticket, cmd => $list, param_hr => {}, expect => 'TRUE', } ); if ( defined $answer_hr->{msg} ) { $self->out(" Server answer: $answer_hr->{msg}\n"); } my $result = $self->member_list( { answer_hr => $answer_hr, member => $testobj } ); $self->record_test_result( { start => 1, title => $title . q{ } . $create . ' result', mode => 'true', true => $result, print => $create . 'should create something', success => 'GOOD: ' . $testobj . ' was created', failure => 'BAD: ' . $testobj . ' was NOT created' } ); $answer_hr = $self->xmlrpc( { title => $title . q{ } . $destroy . ' exec', url => $url, login => $login, ticket => $ticket, cmd => $destroy, param_hr => { object => $testobj }, expect => 'TRUE', } ); $answer_hr = $self->xmlrpc( { title => $title . q{ } . $list . ' list', url => $url, login => $login, ticket => $ticket, cmd => $list, param_hr => {}, expect => 'TRUE', } ); $result = $self->member_list( { answer_hr => $answer_hr, member => $testobj } ); $self->record_test_result( { start => 1, title => $title . q{ } . $destroy . ' result 1', mode => 'true', true => ( not $result ), print => $destroy . 'should destroyed ' . $testobj, success => 'GOOD: ' . $testobj . ' was destroyed', failure => 'BAD: ' . $testobj . ' was NOT destroyed' } ); return; } # @member = $self->member_list({answer_hr => $answer_hr}); # will give back memberUid of answer # # $member_hr = $self->member_list({answer_hr => $answer_hr}); # will give back $member{member} = 1 hash reference # # $true = $self->member_list({answer_hr => $answer_hr, member => $member}); # will give bach TRUE if member is in answer, else will give back FALSE # # optional target_attr may be specified, default = memberUid # sub member_list { my ( $self, $arg_r ) = @_; my $logger = get_logger(__PACKAGE__); my $answer_hr = ( exists $arg_r->{answer_hr} ) ? $self->h( $arg_r->{answer_hr} ) : $self->perr('answer_hr'); my $member = ( exists $arg_r->{member} ) ? $self->l( $arg_r->{member} ) : $EMPTY_STRING; my $task = $answer_hr->{cmd}; $logger->debug("task: $task"); # This is only used for groups # TODO: remove this and use CipUX::RPC::Client my %target_attr = ( cipux_task_list_admin_accounts => 'memberUid', cipux_task_list_student_accounts => 'memberUid', cipux_task_list_teacher_accounts => 'memberUid', cipux_task_list_pupil_accounts => 'memberUid', cipux_task_list_lecturer_accounts => 'memberUid', cipux_task_list_assistant_accounts => 'memberUid', cipux_task_list_professor_accounts => 'memberUid', cipux_task_list_tutor_accounts => 'memberUid', cipux_task_list_members_of_role_account => 'memberUid', cipux_task_list_members_of_admin_account => 'memberUid', cipux_task_list_members_of_student_account => 'memberUid', cipux_task_list_members_of_teacher_account => 'memberUid', cipux_task_list_members_of_pupil_account => 'memberUid', cipux_task_list_members_of_lecturer_account => 'memberUid', cipux_task_list_members_of_assistant_account => 'memberUid', cipux_task_list_members_of_professor_account => 'memberUid', cipux_task_list_members_of_tutor_account => 'memberUid', cipux_task_list_course_shares => 'uid', cipux_task_list_class_shares => 'uid', cipux_task_list_lecture_shares => 'uid', cipux_task_list_seminar_shares => 'uid', cipux_task_list_workshop_shares => 'uid', cipux_task_list_reading_shares => 'uid', cipux_task_list_studygroup_shares => 'uid', cipux_task_list_team_shares => 'uid', cipux_task_list_tutorial_shares => 'uid', cipux_task_list_members_of_course_share => 'memberUid', cipux_task_list_members_of_class_share => 'memberUid', cipux_task_list_members_of_lecture_share => 'memberUid', cipux_task_list_members_of_seminar_share => 'memberUid', cipux_task_list_members_of_workshop_share => 'memberUid', cipux_task_list_members_of_reading_share => 'memberUid', cipux_task_list_members_of_studygroup_share => 'memberUid', cipux_task_list_members_of_team_share => 'memberUid', cipux_task_list_members_of_tutorial_share => 'memberUid', ); my $target_attr = ( defined $target_attr{$task} ) ? $target_attr{$task} : ( exists $arg_r->{taget_attr} ) ? $self->l( $arg_r->{target_attr} ) : 'cn'; #$target_attr = $target_attr{$task}; # memberUid $logger->debug( 'answer_hr: ', { filter => \&Dumper, value => $answer_hr } ); $logger->debug("member: $member"); $logger->debug("target_attr: $target_attr"); my $z = 0; my $user_hr = $answer_hr->{cmdres_r}; my @member = (); my %member = (); my $result = 0; # admins, pupil, ... foreach my $key ( sort keys %{$user_hr} ) { $logger->debug( 'OK, got object key : ', $key ); # cn, memberUid foreach my $value ( @{ $user_hr->{$key}->{$target_attr} } ) { push @member, $value; $member{$value} = 1; if ( $value eq $member ) { $result = 1; } { $self->out(" OK, got obj: $value\n"); $logger->debug("OK, got object $z: $value"); $z++; } } } if ( $member ne $EMPTY_STRING ) { return $result; } return @member if wantarray; return \%member; } } # END INSIDE-OUT CLASS 1; __END__ =pod =head1 NAME CipUX::RPC::Test::Client - libray for test clients =head1 VERSION version 3.4.0.9 =head1 USAGE use CipUX::RPC::Test::Client; =head1 DESCRIPTION This is the library for CipUX XML-RPC test clients. See man page of those clients (for example cipux_rpc_test_client) for details on the clients. =head1 METHODS =head2 create_list_destroy_test =head2 big_entity_test =head2 login =head2 manual_test =head2 member_list =head2 plain_entity_test =head2 probably_delete =head2 record_test_result =head2 run =head2 test_create_list_destroy1 =head2 test_create_list_destroy2 =head2 test_entity1 =head2 test_entity2 =head2 test_list_admin =head2 test_list_users =head2 test_login =head2 test_login_again =head2 test_logout =head2 test_ping =head2 test_plain_entity =head2 test_session =head2 test_sum1 =head2 test_sum2 =head2 test_summary =head2 test_ttl =head2 test_version =head2 xmlrpc =head1 DIAGNOSTICS TODO =head1 EXIT STATUS 1 on failure 0 on success other from XML-RPC server =head1 CONFIGURATION Not needed. =head1 DEPENDENCIES Carp Class::Std CipUX::RPC CipUX::Task Data::Dumper Date::Manip English Frontier::Client Getopt::Long Log::Log4perl Pod::Usage Readonly =head1 INCOMPATIBILITIES Not known. =head1 BUGS AND LIMITATIONS Not known. =head1 AUTHOR Christian Kuelker Echristian.kuelker@cipworx.orgE =head1 LICENSE AND COPYRIGHT Copyright (C) 2008 - 2009 by Christian Kuelker This program 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, or (at your option) any later version. This program 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 this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA =cut CipUX-RPC-3.4.0.9/bin000755001750001750 011432067074 14650 5ustar00ckuelkerckuelker000000000000CipUX-RPC-3.4.0.9/bin/cipux_rpc_list000444001750001750 1105111432067074 17775 0ustar00ckuelkerckuelker000000000000#!/usr/bin/perl -w -T # +=======================================================================+ # || /usr/sbin/cipux_rpc_list || # || || # || Prints CipUX RPC command list. || # || || # || Copyright (C) 2007 - 2009 by Christian Kuelker || # || All rights reserved! || # || || # || License: GNU General Public License - GNU GPL - version 2 || # || or (at your opinion) any later version. || # || || # +=======================================================================+ # ID: $Id$ # Revision: $Revision$ # Head URL: $HeadURL$ # Date: $Date$ # Source: $Source$ package cipux_rpc_list; use 5.008001; use strict; use warnings; use CipUX::RPC::Server::Daemon; use version; our $VERSION = qv('3.4.0.9'); use re 'taint'; # Keep data captured by parens tainted delete @ENV{qw(PATH IFS CDPATH ENV BASH_ENV)}; # Make %ENV safe # +=============================================================================+ # || MAIN || # +=============================================================================+ my $client = CipUX::RPC::Server::Daemon->new( { name => 'cipux_rpc_list' } ); $client->run(); exit 0; __END__ #=========================================================================== #==== Documentation ======================================================== #=========================================================================== =pod =head1 NAME cipux_rpc_list - Prints CipUX RPC command list =head1 VERSION cipux_rpc_list - version 3.4.0.9 =head1 SYNOPSIS cipux_rpc_list =head1 ABSTRACT This is the CipUX XML-RPC list for the CipUX::Task API. The list provides CipUX commands to RPC clients. =head1 OPTIONS I<-c> Same as --cfg I<--cfg> Specifies the configuration. I<-D> The same as --debug I I The same as --help I I The same as --list I Lists. I

The same as --pretty I Pretty output. I The same as --version. I Prints the version. =head1 REQUIREMENTS CipUX CipUX::RPC::Server Getopt::Long Pod::Usage Carp =head1 DESCRIPTION +----------------------------------------+ | xml rpc command | +========================================+ | cipux_task_create_room | | cipux_task_create_student_account | | cipux_task_create_teacher_account | | cipux_task_destroy_room | | cipux_task_destroy_student_account | | cipux_task_destroy_teacher_account | | cipux_task_list_netgroups | | cipux_task_list_rooms | | cipux_task_list_student_accounts | | cipux_task_list_teacher_accounts | | cipux_task_optain_room_network_address | +----------------------------------------+ =head1 USAGE cipux_rpc_list [OPTIONS] =head1 EXAMPLE cipux_rpc_list cipux_rpc_list --list cipux_rpc_list --debug =head1 REQUIRED ARGUMENTS TODO =head1 DIAGNOSTICS TODO =head1 EXIT STATUS TODO =head1 CONFIGURATION TODO =head1 DEPENDENCIES CipUX::RPC::Server::Daemon version =head1 INCOMPATIBILITIES Not known. =head1 BUGS AND LIMITATIONS Supports https only via stunnel4. =head1 SEE ALSO See the CipUX webpage and the manual at L See the mailing list L =head1 AUTHOR Christian Kuelker Echristian.kuelker@cipworx.orgE =head1 LICENSE AND COPYRIGHT Copyright (C) 2007 - 2009 by Christian Kuelker This program 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, or (at your option) any later version. This program 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 this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA =cut CipUX-RPC-3.4.0.9/doc000755001750001750 011432067074 14645 5ustar00ckuelkerckuelker000000000000CipUX-RPC-3.4.0.9/doc/example000755001750001750 011432067074 16300 5ustar00ckuelkerckuelker000000000000CipUX-RPC-3.4.0.9/doc/example/README000444001750001750 212211432067074 17312 0ustar00ckuelkerckuelker000000000000CipUX-RPC examples ================== In this folder you can find some examples in "bin" on how to access the CipUX-XML-RPC server which are referenced by the documentation in the "tectdoc" folder. Some of the examples are solely independent, some others might require additionally Perl modules to work. For example CipUX::XML::Client. It it very likely that this module is already installed on the CipUX XML-RPC server. But in case it is not, you have to install it. Please be aware that the examples are not for production use. They purpose is to study. In this case the study on how the XML-RPC server can be accessed with different commands. If you are searching on how to write good or useful clients you might get something out of the examples if you have no clue at all, but keep in mind the examples are written to explain single rpc commands of the server and not for good client code. We might add those examples to the CipUX::XML::Client module in the future. Meanwhile you can study CipUX-Passwd or CipUX-CAT-Web, or if you prefer other languages then CATweasel (Python) or cipuxPHP (PHP). CipUX-RPC-3.4.0.9/doc/example/bin000755001750001750 011432067074 17050 5ustar00ckuelkerckuelker000000000000CipUX-RPC-3.4.0.9/doc/example/bin/expl_rpc_ping000444001750001750 144411432067074 21764 0ustar00ckuelkerckuelker000000000000#!/usr/bin/perl -w use strict; use English qw( -no_match_vars); use Frontier::Client; my $header_hr = { cipux_version => '3.4.0.0', client_name => 'expl_rpc_ping', client_version => '0.1', rpc_version => '2.0', client_key => '', client_cred => '', gmt_time => time, }; my $pay_hr = { header_hr => $header_hr, login => 'dummy', ticket => 'dummy', cmd => 'ping', param_hr => {}, }; my $http_url = "http://localhost:8001/RPC2"; my $server = Frontier::Client->new( url => $http_url ); my $answer_hr = {}; eval { $answer_hr = $server->call( 'ping', $pay_hr ); }; if ($EVAL_ERROR) { print "Server down\n"; } elsif ( $answer_hr->{status} eq 'TRUE' ) { print "Sever up\n"; } else { print "Server problem\n"; } CipUX-RPC-3.4.0.9/doc/example/bin/expl_rpc_task_member000444001750001750 415611432067074 23323 0ustar00ckuelkerckuelker000000000000#!/usr/bin/perl -w use strict; use CipUX::RPC::Client; use English qw( -no_match_vars); # prep my $rpc = CipUX::RPC::Client->new( { url => 'http://localhost:8001/RPC2', client => 'cipux_rpc_task_create_destroy_test', version => '0.0.1', } ); my $s = {}; my $m = {}; my $id = shift; # start calling eval { $rpc->rpc_ping; }; die "Server is down! $EVAL_ERROR" if $EVAL_ERROR; my $ok = $rpc->rpc_login; if ($ok) { $s = list('student'); create_student($id) if not exists $s->{"\t$id"}; $s = list('student'); $m = list('tutor'); add_to_tutor($id) if not exists $m->{"\t$id"}; $m = list('tutor'); remove_from_tutor($id) if not exists $m->{"\t$id"}; $m = list('tutor'); destroy_student($id) if exists $s->{"\t$id"}; $s = list('student'); $rpc->rpc_logout; } else { print 'No access for ' . $rpc->get_login . "!\n"; print "Wrong password?\n"; } sub list { my $l = {}; my $r = shift; my $cmd = 'cipux_task_list_'.$r.'_accounts'; my $a_hr = $rpc->xmlrpc( { cmd => $cmd } ); my $d_hr = $rpc->extract_data_for_tpl( { answer_hr => $a_hr, use_ltarget => 1 } ); foreach ( @{ $d_hr->{tpl_data_ar} } ) { $l->{"\t".$_->{$d_hr->{ltarget}}}=1; } print ucfirst $r ."s on the system:",sort keys %{$l},"\n"; return $l; } sub create_student { my $s = shift; my $cmd = 'cipux_task_create_student_account'; my $p_hr = { object => $s, }; my $a_hr = $rpc->xmlrpc({cmd=>$cmd, param_hr=>$p_hr}); } sub destroy_student { my $s = shift; my $cmd = 'cipux_task_destroy_student_account'; my $p_hr = { object => $s, }; my $a_hr = $rpc->xmlrpc({cmd=>$cmd, param_hr=>$p_hr}); } sub add_to_tutor { my $s = shift; my $cmd = 'cipux_task_add_member_to_role_account'; my $p_hr = { object => 'tutor', target => $s, }; my $a_hr = $rpc->xmlrpc({cmd=>$cmd, param_hr=>$p_hr}); } sub remove_from_tutor { my $s = shift; my $cmd = 'cipux_task_remove_member_from_role_account'; my $p_hr = { object => 'tutor', target => $s, }; my $a_hr = $rpc->xmlrpc({cmd=>$cmd, param_hr=>$p_hr}); } CipUX-RPC-3.4.0.9/doc/example/bin/expl_rpc_task_sum000444001750001750 163111432067074 22653 0ustar00ckuelkerckuelker000000000000#!/usr/bin/perl -w use strict; use English qw( -no_match_vars); use Frontier::Client; my $header_hr = { cipux_version => '3.4.0.0', client_name => 'cipux_rpc_task_sum_test', client_version => '0.1', rpc_version => '2.0', client_key => '', client_cred => '', gmt_time => time, }; my $pay_hr = { header_hr => $header_hr, login => 'dummy', ticket => 'dummy', cmd => 'cipux_task_sum', param_hr => { summand1 => 4, summand2 => 7, }, }; my $http_url = "http://localhost:8001/RPC2"; my $server = Frontier::Client->new( url => $http_url ); my $answer_hr = {}; eval { $answer_hr = $server->call( 'task', $pay_hr ); }; if ($EVAL_ERROR) { print "Server down\n"; } elsif ( $answer_hr->{status} eq 'TRUE' ) { print "Sever up\n"; } else { print "Server problem\n"; } use Data::Dumper; print Dumper($answer_hr); CipUX-RPC-3.4.0.9/doc/example/bin/expl_rpc_task_create_destroy_two_times000444001750001750 336511432067074 27163 0ustar00ckuelkerckuelker000000000000#!/usr/bin/perl -w use strict; use CipUX::RPC::Client; use English qw( -no_match_vars); # prep my $rpc = CipUX::RPC::Client->new( { url => 'http://localhost:8001/RPC2', client => 'cipux_rpc_task_create_destroy_test_two_times', version => '0.0.1', } ); my $s = {}; my $id1 = shift; my $id2 = shift; chomp $id1; chomp $id2; # start calling eval { $rpc->rpc_ping; }; die "Server is down! $EVAL_ERROR" if $EVAL_ERROR; my $ok = $rpc->rpc_login; if ($ok) { list_students(); print "create [$id1]\n"; create_student($id1) if not exists $s->{"\t$id1"}; list_students(); print "create [$id2]\n"; create_student($id2) if not exists $s->{"\t$id2"}; list_students(); destroy_student($id1) if exists $s->{"\t$id1"}; list_students(); destroy_student($id2) if exists $s->{"\t$id2"}; list_students(); $rpc->rpc_logout; } else { print 'No access for ' . $rpc->get_login . "!\n"; print "Wrong password?\n"; } sub list_students { my $cmd = 'cipux_task_list_student_accounts'; my $a_hr = $rpc->xmlrpc( { cmd => $cmd } ); my $d_hr = $rpc->extract_data_for_tpl( { answer_hr => $a_hr, use_ltarget => 1 } ); $s = {}; foreach ( @{ $d_hr->{tpl_data_ar} } ) { $s->{ "\t" . $_->{ $d_hr->{ltarget} } } = 1; } print "Students on the system:", sort keys %{$s}, "\n"; } sub create_student { my $s = shift; my $cmd = 'cipux_task_create_student_account'; my $p_hr = { object => $s, }; my $a_hr = $rpc->xmlrpc( { cmd => $cmd, param_hr => $p_hr } ); } sub destroy_student { my $s = shift; my $cmd = 'cipux_task_destroy_student_account'; my $p_hr = { object => $s, }; my $a_hr = $rpc->xmlrpc( { cmd => $cmd, param_hr => $p_hr } ); } CipUX-RPC-3.4.0.9/doc/example/bin/expl_rpc_login_session_logout000444001750001750 136011432067074 25270 0ustar00ckuelkerckuelker000000000000#!/usr/bin/perl -w use strict; use CipUX::RPC::Client; use English qw( -no_match_vars); # prep my $rpc = CipUX::RPC::Client->new( { url => 'http://localhost:8001/RPC2', client => 'cipux_rpc_login_session_logout', version => '0.0.1', } ); my $s = {}; # start calling eval { $rpc->rpc_ping; }; die "Server is down! $EVAL_ERROR" if $EVAL_ERROR; my $ok = $rpc->rpc_login; if ($ok) { my $t1 = $rpc->get_ticket; print "\tfirst session [$t1]\n"; my $t2 = $rpc->rpc_session; if($t2){ print "\tsecond session [$t2]\n"; $rpc->rpc_logout; }else{ print "\tno second session\n"; } } else { print 'No access for ' . $rpc->get_login . "!\n"; print "Wrong password?\n"; } CipUX-RPC-3.4.0.9/doc/example/bin/expl_rpc_task_list000444001750001750 150711432067074 23024 0ustar00ckuelkerckuelker000000000000#!/usr/bin/perl -w use strict; use CipUX::RPC::Client; use English qw( -no_match_vars); # prep my $rpc = CipUX::RPC::Client->new( { url => 'http://localhost:8001/RPC2', client => 'expl_rpc_task_list', version => '0.0.1', } ); # start calling eval { $rpc->rpc_ping; }; die "Server is down! $EVAL_ERROR" if $EVAL_ERROR; my $ok = $rpc->rpc_login; if ($ok) { my $cmd = 'cipux_task_list_student_accounts'; my $a_hr = $rpc->xmlrpc( { cmd => $cmd } ); my $d_hr = $rpc->extract_data_for_tpl( { answer_hr => $a_hr, use_ltarget => 1 } ); print "Students on the system:\n"; foreach ( @{ $d_hr->{tpl_data_ar} } ) { print "\t$_->{$d_hr->{ltarget}}\n"; } $rpc->rpc_logout; } else { print 'No access for ' . $rpc->get_login . "!\n"; print "Wrong password?\n"; } CipUX-RPC-3.4.0.9/doc/example/bin/expl_rpc_task_create_destroy000444001750001750 272111432067074 25064 0ustar00ckuelkerckuelker000000000000#!/usr/bin/perl -w use strict; use CipUX::RPC::Client; use English qw( -no_match_vars); # prep my $rpc = CipUX::RPC::Client->new( { url => 'http://localhost:8001/RPC2', client => 'cipux_rpc_task_create_destroy_test', version => '0.0.1', } ); my $s = {}; my $id = shift; # start calling eval { $rpc->rpc_ping; }; die "Server is down! $EVAL_ERROR" if $EVAL_ERROR; my $ok = $rpc->rpc_login; if ($ok) { list_students(); create_student($id) if not exists $s->{"\t$id"}; list_students(); destroy_student($id) if exists $s->{"\t$id"}; list_students(); $rpc->rpc_logout; } else { print 'No access for ' . $rpc->get_login . "!\n"; print "Wrong password?\n"; } sub list_students { my $cmd = 'cipux_task_list_student_accounts'; my $a_hr = $rpc->xmlrpc( { cmd => $cmd } ); my $d_hr = $rpc->extract_data_for_tpl( { answer_hr => $a_hr, use_ltarget => 1 } ); $s = {}; foreach ( @{ $d_hr->{tpl_data_ar} } ) { $s->{"\t".$_->{$d_hr->{ltarget}}}=1; } print "Students on the system:",sort keys %{$s},"\n"; } sub create_student { my $s = shift; my $cmd = 'cipux_task_create_student_account'; my $p_hr = { object => $s, }; my $a_hr = $rpc->xmlrpc({cmd=>$cmd, param_hr=>$p_hr}); } sub destroy_student { my $s = shift; my $cmd = 'cipux_task_destroy_student_account'; my $p_hr = { object => $s, }; my $a_hr = $rpc->xmlrpc({cmd=>$cmd, param_hr=>$p_hr}); } CipUX-RPC-3.4.0.9/doc/example/bin/expl_rpc_session000444001750001750 707011432067074 22513 0ustar00ckuelkerckuelker000000000000#!/usr/bin/perl -w -T use strict; use CipUX::RPC; use English qw( -no_match_vars); use Frontier::Client; delete @ENV{qw(PATH IFS CDPATH ENV BASH_ENV)}; my $rpc = CipUX::RPC->new(); my $login = $rpc->login_prompt( { prompt => 'Login: ' } ); my $password = $rpc->password_prompt( { prompt => 'Password: ' } ); my $header_hr = { cipux_version => '3.4.0.0', client_name => 'cipux_rpc_session_test', client_version => '0.1', rpc_version => '2.0', client_key => '', client_cred => '', gmt_time => time, }; my $pay_hr = { header_hr => $header_hr, login => $login, ticket => 'dummy', cmd => 'login', param_hr => { password => $password, }, }; # login my $http_url = "http://localhost:8001/RPC2"; my $server = Frontier::Client->new( url => $http_url ); my $answer_hr = {}; eval { $answer_hr = $server->call( 'login', $pay_hr ); }; my $continue = 0; my $ticket = 0; if ($EVAL_ERROR) { print "Server down\n"; print "The server complains: $EVAL_ERROR\n"; } elsif ( $answer_hr->{status} eq 'TRUE' ) { print "Sever up\n"; if ( exists $answer_hr->{cmdres_r}->{ticket} and defined $answer_hr->{cmdres_r}->{ticket} ) { $ticket = $answer_hr->{cmdres_r}->{ticket}; $continue = 1; } } else { print "Server problem\n"; } # if login OK => renew session if ($continue) { my $session_ok = 0; print "ticket before session renewal: [$ticket]\n"; my $pay_hr = { header_hr => $header_hr, login => $login, ticket => "$ticket", cmd => 'session', param_hr => {}, }; eval { $answer_hr = $server->call( 'session', $pay_hr ); }; if ($EVAL_ERROR) { print "Server down\n"; print "The server complains: $EVAL_ERROR\n"; } elsif ( $answer_hr->{status} eq 'TRUE' ) { print "Session OK\n"; $session_ok = 1; if ( exists $answer_hr->{cmdres_r}->{ticket} and defined $answer_hr->{cmdres_r}->{ticket} and $answer_hr->{cmdres_r}->{ticket} ) { $ticket = $answer_hr->{cmdres_r}->{ticket}; } } else { print "Server problem\n"; } print "ticket after session renewal: [$ticket]\n"; my $old_ticket = undef; if ( exists $answer_hr->{ticket} and defined $answer_hr->{ticket} and $answer_hr->{ticket} ) { $old_ticket = $answer_hr->{ticket}; } print "the old ticket was: [$old_ticket]\n"; # log out $pay_hr = { header_hr => $header_hr, login => $login, ticket => "$ticket", cmd => 'logout', param_hr => {}, }; eval { $answer_hr = $server->call( 'logout', $pay_hr ); }; if ($EVAL_ERROR) { print "Server down\n"; print "The server complains: $EVAL_ERROR\n"; } elsif ( $answer_hr->{status} eq 'TRUE' ) { print "Session OK\n"; $session_ok = 1; if ( exists $answer_hr->{cmdres_r}->{ticket} and defined $answer_hr->{cmdres_r}->{ticket} and $answer_hr->{cmdres_r}->{ticket} ) { $ticket = $answer_hr->{cmdres_r}->{ticket}; } } else { print "Server problem\n"; } } use Data::Dumper; print Dumper($answer_hr); exit 0; $pay_hr = { header_hr => $header_hr, login => 'dummy', ticket => 'dummy', cmd => 'cipux_task_sum', param_hr => { summand1 => 4, summand2 => 7, }, }; $answer_hr = $server->call( 'task', $pay_hr ); print Dumper($answer_hr); CipUX-RPC-3.4.0.9/doc/dep000755001750001750 011432067074 15415 5ustar00ckuelkerckuelker000000000000CipUX-RPC-3.4.0.9/doc/dep/depgraph.svg000444001750001750 1157411432067074 20115 0ustar00ckuelkerckuelker000000000000 ]> test node1 CipUX::RPC node2 CipUX::RPC::Server node1->node2 node6 CipUX::RPC::Test::Client node1->node6 CipUX CipUX CipUX->node1 node2->CipUX node3 CipUX::Task node2->node3 node4 CipUX::RBAC::Simple node2->node4 node5 CipUX::RPC::Server::Daemon node2->node5 node6->node3 CipUX-RPC-3.4.0.9/doc/dep/depgraph.dot000444001750001750 126611432067074 20061 0ustar00ckuelkerckuelker000000000000digraph test { graph [ratio=fill]; node [label="\N"]; node1 [label="CipUX::RPC", shape=box]; CipUX [label=CipUX]; node2 [label="CipUX::RPC::Server", shape=box]; node3 [label="CipUX::Task"]; node4 [label="CipUX::RBAC::Simple"]; node5 [label="CipUX::RPC::Server::Daemon", shape=box]; node6 [label="CipUX::RPC::Test::Client", shape=box]; CipUX -> node1 [color=red, dir=backward]; node1 -> node2 [color=red, dir=backward]; node1 -> node6 [color=red, dir=backward]; node2 -> CipUX [color=black, dir=forward]; node2 -> node4 [color=black, dir=forward]; node2 -> node5 [color=red, dir=backward]; node2 -> node3 [color=black, dir=forward]; node6 -> node3 [color=black, dir=forward]; } CipUX-RPC-3.4.0.9/doc/dep/depgraph.png000444001750001750 7306211432067074 20102 0ustar00ckuelkerckuelker000000000000‰PNG  IHDR`û Û÷¹bKGDÿÿÿ ½§“tIMEÙ ;.Ý‚£ IDATxœìÝw|Õú?ðϦ’¤H Az€ÐÒ¡ $†^)_ "ˆ×êýYî½WÄ«H±€Š€"(R -B(I $¡÷@B é½ßÇ]éewv³Ÿ÷뵯dwfgŸÝÌlæ™sÎsTB""""""Ò:¥ """""2LÀˆˆˆˆˆˆt„ ‘Ž˜)5^111HLLT: Ò"wwwøûû+‘ÁP±iË”)S°nÝ:¥Ã -šS&`DDDDµÇ.ˆ¤W–/_ޤ¤$¼òÊ+•& $ùª9sæ C‡øä“OœœŒèèhDFF–k¥ª¯víÚîÞ½[£õ½½½'NœÐ} R©`kk‹W_}nnn°°°@·nݰ~ýú‹ÓÒÒ‹/FNNæÎ‹Y³faåÊ•055-·n@@+½_•óçÏz÷î]£õ‹‹‹5ñ©Õæ3%""""íbFzåÌ™3€Ž;V»n—.]páÂ…2;v €C5sæL\½z hÒ¤ &Mš„ððð‹5,, aaaøùçŸ1~üxtêÔ©ÂõJKKQZZZéýŠäææ"** Ï>û,,--ñ¿ÿý¯F1hðàÁ€ˆˆˆJ×9räbbb*½ÿ ukž ‚‚‚дiSÄÆÆ¢ÿþUÆ‘——‡Ý»wcÖ¬YhÒ¤ Þÿ}Ͳ†üL‰ˆˆˆ¨~˜€‘^Q' 999 º]___ÀÑ£Gl›×¯_Ç’%Kзo_ìÛ·›7o®÷6…B )) vvvÈÌÌD—.]ª|ŽºËåÔ©Sáããƒèèhøûûk–kë3%"""¢ÚcFzElœ={¶A·«.Ÿ••UáòŠ* !4cË*2sæLü÷¿ÿÅ7ß|333¼ùæ›(,,lx]]]ñùçŸãÈ‘#˜7o^•ë !PRR‚Û·ocÆ åJàkë3%"""¢ÚcFzeôèÑPmkÒØ±ckµÝÔÔT€““S™ÇíííË,Ð;wàìì\áö~øá4iÒÓ¦MC×®]1kÖ,\¼xK—.­U\U™:u*ÆŽ‹ hÆvÕ…¶>S""""ª=&`¤WfÏž www|þùçšâ9wî\­¶ .ó¸ú~E%Ø7oÞŒ¾}û–{üöíÛ˜?>–/_®yìý÷߇ƒƒþûßÿÖj2ãê,_¾ööö˜6mšf,Wmië3%"""¢ÚcFz¥Y³fؾ};œÑ¿ÍV%%%HKKÃîÝ»1aÂ\¿~½Úm¥¦¦¢´´gΜÁ;ï¼KKËr“$¿ÿþû°¶¶Æœ9sŽ‚‚¤§§cõêÕ˜7oþýï—Ûî‹/¾ˆ?ü-Z´Ð<æàà€yóæ!##ÿú׿ʬïïï_¦*áÃ÷«Ò¢E ,[¶ W¯^ÅìÙ³kôœ‡5ägJDDDDõÃŒôN÷îÝ·ß~ëÖ­C=`aaWWW¼ð (**‡~ˆ_~ùšçyxxà§Ÿ~˜˜˜ W¯^°´´„\\\°gÏôêÕ«ÌkõêÕ ÑÑÑÆSO=[[[tèÐ[¶lÁÎ;ˬ¿wï^´mÛ›6mÂk¯½†U«Vi–?Ÿ~ú)ÙjÕ¥K?~Ê#{ðþûï¿wwwÍý–-[bêÔ©eÖŸ0a&Mš„uëÖá‘GÁ£>Zæ9... mÏ”ˆˆˆˆ´K%*ª>@Ô¦L™X»v­N_W¥RÁÄÄ%%%:}]c£Ôß—ˆˆˆÈ±Œ¥ê&:&""""R0""""""aFFzz:<==5÷Û´iSfœ‘ÒÌ”€¨¡ØÙÙUYfˆˆˆˆHil#"""""Ò&`DDDDDD:ÂŒˆˆˆˆˆHG˜€é0""""""aFDDDDD¤#LÀˆˆˆˆˆˆt„ ‘Ž0#"""""Ò&`DDDDDD:ÂŒˆˆˆˆˆHG˜€鈙ÒPã¶aÃŒ3–™™°»rÍ’“‘Ùº5n{y)Õ‚iQL Phk«ylÆ ˜8q¢‚Q&`Ô°23S§€„ü_\fÁëñÇaÿ÷â(C*¢¶¼ü÷Ï¥ŠF¡ÏX `%äû»¨l8DDDDK%„JA¨ 8}HH ׉ÀÉ“ÀÍ›r¹™`b>j¹ºÊç4o®LÌÚ6eŠü¹v­²qhCVðÝwÀâÅÀõëÀ˜1Àܹ@PÒ‘&`T{O> üü³üÝÄ07—‰Ve»’¹¹L¾Žœu§®5æL­¤øõWàÓO#G™ˆ˜š*‘Þcª½3îÿ^Z*[Ã*K¾LM`ÏžÆ| SS`âD &ˆŒÜÜ€I“€N€%KdKUŠ ÕÞàÁ@h¨lÙªŠJ%»"†‡:è&6Òà``Ó&àÜ9`øpàwwwàÿý? )Ié興ˆˆô0ª›… ew´ª¨TÀ/¿ÈnjÔxuè|õ˜¼ñðý÷À#ӦɱDDDD¤ÁŒê¦}{ÙVU+Ø_£Fé.&R–ƒðöÛÀÕ«À7ßýôî  lß^y7U""""#ÂŒjo×.ÀË 8tHv1|˜J%»¡½ð‚îc#åYZO?-°ðpY¨eäH {wYI1?_鉈ˆˆÃŒj.1Q`>\žLŸ=+ÇýüP¶¦~ü1tí Œ-÷5"""¢FŽ U,'øÇ?d‹V§NÀéÓÀ»ïÊñ=›:xüq9/”µµîc%Ãcc#÷¯sç€ dwÄAƒ9Éwq±Òi0*oÿ~Ùeì§Ÿ€¶nÚ¶­|}`ýzN´LµgbŒ' ºÄÄíÚÉ„¾];àÓOÌL¥#$"""jPLÀ辜ॗdKDÏž²Õkòd¥£"cáç'ç;?^êhÓ˜;WŽ#"""j˜€‘´¿LºÖ­Ö¬6o\\”ŽŠŒQ»vÀ¢ErœØÛoˤ¬CàÉ'¸8¥£#"""ª&`Æ®¨xóM9Ö«{wàÔ)`Ê¥£"ììdá—Ë—U«ä´>>@ÿþÀ¶m@i©ÒÕ0cvå Я°l°r¥¬`ز¥ÒQ•en.Ç…?ìÝ 4kŒ#«'.[äæ*!Q13Vë×½zɰ¸8à©§”Žˆ¨zƒ¿ÿ.§C–óÐyxï½ܺ¥ttDDDDÕbflrs3äxš§Ÿ¢¢d™y"CÒµ+ðí·²8ÇìÙÀW_ÉJÏ='‹Çé)&`Æ$!ðõ•6¶l–,©x^/"CÑ¢…œ$<1Xº8xPŽe öìQ::"""¢r˜€‹Ÿüý''àÄ `Ô(¥#"j8VVÀóÏËÖ¯-[€‚`èP woà‡€ÂB¥#$"""À¬ñ+)‘U'O^|ˆˆZ·V:*"í01‘""€ØX KÙ-ñ‘G€ùó”¥#$"""#Ǭ1KK€/¿”ó{ý©ÒQ醷7ðÓOÀ¥Krj…O>‘;fÎΜQ::"""2RLÀ+õx¯³g帘I“”ŽˆHmÚÈ‹ׯ˟þ të„„;wB(!&`ѦM@` àî.»aõî­tDDʳµ•ÝpÏž¶n•S0„„È¢ß|äå)!&`͇'Ó§»vÉ¢DtŸ‰ 0r¤œÔù¯¿dqš—_–,ÞyHNV:B"""jĘ€5ÅÅr~¯yóäüH‹ffJGE¤ßzôV®”󉽸"ðÝw²`Ç´ir‚r"""¢Æ¬1ÈΖWôׯ~ûM¶~Q͹¸È‹×®Ë—'O>>@ÿþÀ¯¿Êj¢DDDD € ˜¡»ySž$ž< ìß/Ç´QÝXZÏ<#»&îÙ4o.»ôvê$[•33•Žˆˆˆ 0Cvê '™Šb± ¢†4dˆlQ>{ Þ}WVT|í5àòe¥£#"""ÅÌPEFÁÁ@ûö²Ì¼»»Ò5N;_|ܸ!‹tüú«l?^‡DDDDµÀÌíÜ)¯È‡„áá€Ò5~vvÀ›oʉ׭“Õ cÅ~üQ¶DUƒ ˜¡Ùº5JN¬¼f `a¡tDDÆÅÌ xüqÙí7*J¶B?󌬞øá‡@JŠÒ‘cfH~þY˜1X±05U:""ã «^¾ L |ú©ì[±B&_Ÿ~ÊäK=ý4`n^ùrss¹·=€ï¾eâµr%ж­l ‹‹«ùv„sÿ‘Ac¦¯Ö­fÍæÍæÌQ:ªÈOEE•//*’ë@‹² ñµkÀ×_ ²kp0°iPRRõówíºwçÜcDDDŽ ˜>úõWàÿþOÜø÷¿•ކ*Ó¹³lݨ¨¸‚J%—uî¬û¸H¿YZʲõ'NÈñaöö²¬}ÇŽÀ¢E@ffÅÏûßÿäÏaÃäø2"""2HLÀôÍŽÀ“O³g³Ú¡!xꩊ§05•ˈª2x0°mpö,&/¸´i# v<8vðôiàÏ?åïEEÀèѲü=áÐ'þ Œ!'Y^±‚e« Ar2кµŸó • ¸qƒ…¨vÒÓo¿¾øBî?£GËdìûïåÄëê.¯*•¼}ó 0}º²1Q­0Óññr,Hh(ðãœdÙ‡ËÒã`b(®âb`ófÙ%1*J&[}U«TÀÂ…²XvAÔ7n=&çŠúþ{&_†fÚ´²­•*•|Œ¨®ÌÌ€‰ebÿÌ32©¯ˆ²Hç$""2lSZF†lAd‹IóæÊÆCµ—𠏏ÈV @ž<ß¾ 88(¾¼<Ù5=½êõT*àå—e‹».é5¶€)©°7HK“U͘|&`øp™x™™Éß™|QCøñÇÊ«">HàóÏgŸ­¾œ=)Š ˜’fÍ¢£e´Ö­•ކêcÊÙV\,'ª/!€çŸ¿?¶°:¥¥²PÇãË‹;DDD¤—Ì”Àh-Xüðƒœó«wïjWß°aƒ‚¢º2+)ÁØ¿ß\R‚bþ½ôÚĉµ²ÝÄÄDÄÄÄ4ȶì®^Å`KK˜T¸¼ÔÔTŽ &%%2a+)~ý%¶¶Øºr%J,-$2.Ú:>ˆˆHâ0%DDC†ÈRÓ/¾X£§¨8®Cïùû§Ÿ¢QPMhëkoݺu˜¢…PSNßœ´øû§Ó·Ö\tüû9ŒÑàÑPcÇÓ""íb ˜®edȪfãÇ×8ùR[»v-&Ož¬¥À¨Þ¶oˆ°0…¡Êh+Az˜>œÀ§¦"ÝÔ”cK©Ætu|;&`ºöÊ+r|ÆòåJGB mØ0¥# º…`ˆˆˆô0]Ú²EÎóµmàä¤t4ÔÐÌÍ•Ž€ˆˆˆˆô« êÊíÛÀÌ™²LôÈ‘JGCDDDDD `¦+³fÖÖÀâÅJGBDDDDD aD]ض غØ»hÚTéhˆˆˆˆˆH!lÓ¶Ü\àå—å伃) )ˆ ˜¶ýç?²ôü§Ÿ* )Œ]µéÌ`áB9îËÅEéhˆˆˆˆˆHalÓ!€Ù³^½dõC"""""2zlÓ–ŸbbSS¥£!"""""=À0mÈÏÞz ˜>ðöV:"""""ÒLÀ´á‹/€{÷€÷ßW:"""""Ò#LÀZj*0>0w.в¥ÒÑ‘aÖÐþû_ÀÒxýu¥#!"""""=Ã" éêU૯dÙy[[¥£!"""""=ð†ôî»@»vÀsÏ)‰FFF.\ˆAƒÁÙÙæææ°´´D«V­àçç‡3fhÖ BPPP­¶¿dɸººB¥RA¥RÁÕÕK–,Ñ,ÿàƒЪU«rË^~ùe4mÚ*• fffðòò™3gŸŸ???XZZ¢I“&7n\âPß,,,àââ‚áÇã—_~©ö9æææhժƇ¨¨¨z¦Õy8ÞÊnJ«É{þùçŸáîî^.æºìWƈǪ~«°mÛ6 >ŽŽŽ033ƒ­­-:wcÇÖj;DDdä5Œ³g…01bãF­l€X»vm­žsìØ1ѺukѲeK±lÙ2qõêUQXX(²²²D\\œX°`pttÔ¬ ë_U»SEË®_¿.lll„›››ÈÉÉÑ<^PP |}}ʼn'êGvv¶ˆŠŠAAA€X°`A•ÏÉÉÉû÷ïžžžÂÔÔTìØ±£ÌºµýL«,nß¾- ÊÄRTT$rssÅíÛ·Eppp­?‡†TÛ÷üðß >ûUC[»vm•û©RÛ籪ÿÇê—_~)ˆÇ\œ={V‰;wîˆ~øAX[[×úýë#mDD$ñ›¶¡L›&„——¥¥ZÙ|m°ôôtáææ&ìììÄ•+W*]oذa ]ÝNê„bþüù€˜7ožæ±·ÞzK|øá‡ ǹsçáææV£ç:tHþþþšÇ´ñ™>÷ÜsÕÆòð:u•˜˜(llljõœº¼çêö…†P—÷"„~&`}ú@¥RÁÖÖ¯¾ú*ÜÜÜ`aanݺaýúõ §¥¥%/^ŒœœÌ;³fÍÂÊ•+ajjZnÝ€€Vz¿*çÏŸôîÝ»FëkâS«ÍgZYŒµ‰ùaB¼÷Þ{hÑ¢lllðÔSO!??_³<)) ?þ8`jj {{{ôîÝ)))ðóóÃæÍ› Ìß¹ºøêòžÕ*Ú¯ªzèÞ½;T*œñË/¿`ðàÁ°²²‚»»;þøã*ß‹¡â±Z–¾«þþþ€‘#Gâ§Ÿ~BQQQ¥ÛªlOMM…§§§fœ]RRfΜ ;;; :TówussCxx8Ö¬Y£—7a„:o›ˆˆôŒ²ù_#pé’lýZ·N«/ƒZ¶€9:: "==½V¯ñà.¡¾úôiQXX(Î;'úôé#”oñðs+ÚvUÂÂÂñÁTºŽ¯¯¯ðóó«ô~EqäääˆÃ‡ aii)öïß_alÇ.ˆáÇk«ËgZ“˜«‹Eí믿֌Q_©~°;˜zìÌÆEaa¡¸yó¦8p`•Û®.¾†Üjú>Ôë9rDäåå‰Õ«W ¢cÇŽ5úœª¢-`®¦î:ùÏþSóسÏ>+>úè£zo»&˜€é¿ië#9Y !V¯ÖúKÕ6³³³DFFF­^£º“ºÌÌLÍÕߪž[Ѷ+“˜˜(ÜÝÝEß¾}ñ믿Ö8æŠ^çÁ×JJJvvvÂÏÏOUùõI~‹-Ä„ ÊUv«ËgZßøÔ¬Y3@dgg‹ââb@XYYi–«OÌ:uê$Ö¬Y#òóók¼íÊhc?ªî}TgMöÍšÐnjǪdhÇjll¬˜>}ºP©T€˜={¶fY]öq!äç«R©DÛ¶m…r<œ““S™B"uÝvM0#"Ò ~ÓÖÇ»ï Ѫ•—צÚ&`ê“ñ˜˜˜Z½Fu'uêøfff>·´‚*¥¥¥B¥RUúº¡¡¡â‡~§Nfff¢C‡š²ìµUQÌkÖ¬Ä;ï¼SãçT¤.ŸimU‹‰‰I…-jW¯^£FҬתU«2Ý£êrb¦ý¨º÷al Õû éXU[¶l™P¦¤}]öqµ"::ZìÙ³§LuÇún»:LÀˆˆtƒE8ê*/X¾˜3°°P:šrF šb•©í¢©©©''§2ÛÛÛ—Yþ ;wîÀÙÙ¹ÂíýðÃhÒ¤ ¦M›†®]»bÖ¬Y¸xñ"–.]Z«¸ª2uêTŒ; ,ÀÁƒë¼m}¦5ecc(**‚O „Ð,÷ððÀÖ­[qíÚ5<ñĸyóf­'š}˜6ÞsuïÃØðX½OŸÕ!C†TøøSO=ÈÉÉѤ¢˜…âöíÛÂÉÉI´mÛ¶ÜÀüÊžó°º|¦µUU,¾¾¾€8sæL…ËlíRw?377¯Ñ¶+£ý¨º÷QQœ5Ù7kB[Àx¬–¥¯Çª½½}…]#ãããÑ·o_ÍcuÙÇÕÒÒÒ„¥¥¥puunnnåÆtÖgÛÕa ‘nð›¶.JJ„èÔIˆ—^ÒÙKÖ6Bž´mÛV899‰Þlj¶ IDATeË–‰7nˆââb‘šš*víÚ%Æ/lmm˼FE'u÷îÝ%%%âôéÓ¢sçÎÂÒÒR?~¼Ìk?~\X[[ ±cÇ‘ŸŸ/ÒÒÒĪU«„‹‹K¹õ…büøñbãÆå_ºt© fÍšUæq???PéýŠÞÃ6lØ ˆÉ“'×ø9«ígZ“˜kËòåË&’““EQQQ™“°îÝ»‹sçΉ‚‚±nÝ:@ôë×O³ÜÞÞ^±±±šÛšÄ×PûQMßGM°ŠÞKMèc&Õ‡éã± @‹;wЬ¬,‘——'öïß/¼¼¼„……E™ uÙÇ4nÜ8@ 0 Ü²ún»*LÀˆˆtƒß´u±e‹&&B\¸ ³—¬K&„YYYâ³Ï>ÁÁÁÂÁÁA˜˜˜ˆ&MšˆöíÛ‹Q£F‰%K–ˆõë× wwwÍ?nwwwÍ ¼‰‰‰hÓ¦033ÖÖÖ¢ÿþâÀ¾ÖÉ“'Å„ D‹-„™™™ptt£G.7@~Ïž=ÂÃÃCmÚ´+W®Ô,;wî\™X:wî¬)Éìïï_æ„èÁûóæÍmÚ´Ñ<ÏÅÅEL™2¥\Œ“&MDÛ¶mÅ!CÊ<§E‹"$$¤A>ÓŠb¬è¾ÚСCE‹-ÊÄ2tèÐrë}øá‡¢M›6ÂÔÔTxxx”©–6|øpáââ"LLLDóæÍÅèѣŵk×4Ë?ûì3amm-š5k&FŒQ«øê²=|SïW•½üü|Ñ­[·2ûèèhѹsgÍcݺu999¾—šÐ×L«Ó·cuéÒ¥bäÈ‘ÂÃÃCXXX333áêê*žxâ‰2eãÕ*ÚÇïÝ»'<==ËüýÖ¯__î¹›6mIJeË*|_õÙvU˜€é†Jv¯µGlm-[tö’*• k×®ÅäÉ“uúš&&&())ÑÙkiÓºuë0eÊ­™Ñöö+Ãc•‚Rû/‘±1S:ƒsáìÞ­t$:QZZªtDT>‰‰‰€®]»¢S§N¸råŠÂ‘‘>cVu÷ÃÍ›•ŽDcÆ 077W: "ƒUÛª³gÏ"""ûöíCDDîÝ» xzzÂËË ÿøÇ?ЫW/ôèÑ...X·nvîÜ©-aiii°··W: Òfyy0ÏÍEž£c•ëéÃ~KDd TB¡tzéË/·ßîÜÑ‹0•J¥tDFE_{ÅÅÅ8qâ<ˆÈÈH|III077‡···&Ùêׯ”·Îâãã±páBüôÓOpttÄ+¯¼‚™3gÂÎÎNéÐôÂK/½„ƒâøñãJ‡b˜.^”Ó¯lÙÄÄÈä+4T¶Œ……Üψˆ°‡åçÎÎÀ¢EÀsÏ)  Î;cÚ´ix÷Ýw•¥ÑJMMETT¢¢¢pèÐ!=z999ppp@`` ‚‚‚ ŸF1hÏž=øôÓO±k×.xzzbΜ9˜:u*,õ @>Ù¶mÆŽ‹Û·oÃÉÉIép [r2°m›lûóO9Flà@Ù26z4Ъ•Ò,&`Û¾5 ¸qƒÿ`¨N €ž={béÒ¥J‡Òhœ?QQQˆŒŒDTTΞ= !<==ˆ¾}û"00]ºt1˜ñ[ÕaaÚËÌÌ„££#Ö¬YƒI“&)N㑞.{„lÙ„‡yy@@€l;èÐA鉈 °‡=ÿ>> BPPáìì¬t¸ N]XcÑ¢EHNN6êÂuÑ·o_xzzbÅŠJ‡Ò8åå»vÉdì·ß€{÷/¯ûÉXïÞJGHD¤÷8óöofÍR: 2`­ZµÂÉ“'•à ãäÉ“ˆŽŽÆ‘#GpäÈMë–‡‡ðöÛo#00Ð ÊÁ× k4Œ¡C‡bÕªUJ‡ÑxYYÉ.ˆ£GÅÅ@d¤ì¦¸z5ðŸÿmÛÊDlÌ o_VT$"ª[ÀôèÄÆÊyQˆêà­·ÞÂü¿þúKéPôNbb"¢££5­[qqqÈËËC³fÍàëë øûûÃÏÏ-[¶T:\x°°†““^~ùeÖ¨‡èèh"!!ݺuS:ã!„üß¹y³l;shÑBvé3xôQ€c‰ˆ°¬¬ðpùƒ](¨š7oŽœœ¥ÃP\VVŽ=Z&áºuëÌÌÌЭ[7àÙgŸ…ŸŸ<==abb¢tÈ:õpa¯¾ú Ó¦Mƒ………Ò¡4___888 <<œ ˜.©T€¯¯¼ÍŸœ=+“±Í›ï¾“Sº<ö˜l š5S:b""ŰìAC†ÈÂ?þ¨t$dÀ>ÿüsÌŸ?7oÞT:)))ABB‚fìVLL Μ9ƒÒÒR´nÝþþþ€ŸŸ¼½½acc£tÈŠ`a ݘ2e nݺ…½{÷* ²¨•zâçýûe·Ä!CdËØèÑòÂ'‘a¦– 89É+uS¦( °•+Wâµ×^CFF†Ò¡hMRRbbb4­[qqqÈÉÉ­­-|||Êt%tssS:\ÅeeeaÅŠX´hnÞ¼ÉÂZ¶fÍ̘1)))°µµU:zн{²¢âæÍ²˜Ga!tÜØ#(!‘Ö1SS—Ÿ¿y“Wã¨^Ö¯_I“&¡±Z·nÝBll,âââ‡ØØXܼy¦¦¦ðôôÔ´nùûû£k×®0å { ÖPÆ;wЪU+lÚ´ cÆŒQ:ªLn.°c‡lÛ¾HK“CÆŒ‘·=”ŽˆH+˜€©½ôpèpì˜Ò‘Û¾};FŒ¬¬,ƒ»ú~çÎM’¥þ™””hß¾=|||àíí ___x{{£iÓ¦ G¬ŸXXCyþþþèÓ§–-[¦t(TEEÀ¾}À¯¿Ê  ““öíqãd2Ù8Q"j¼X„Cmÿ~`Ø0¥£ F Ù߃ËÕ]òôUJJJ™V­ØØX\¿~ðÈ#ÀÛÛ/½ô¼½½áããÃä¡.¬±lÙ2L:•…50|øp|ÿý÷J‡A5en *o_~ 9r¿ˆÇ'Ÿ-[Êñbcǃ<¦ˆÈ€± }Ò[´_ô£F) ¸cÇŽÁÛÛ—.]B»ví”––¦I²ÔI×Õ«Wš$ËÛÛÞÞÞpttT6`ÂÂú)&&ˆG÷îÝ•‡ê#!AvSܼYöR±³“ÇŒ‘?´¨.¶€ÀÁƒògp°²qP£ ž,X©RôéééeZ¶âââpùòe@›6màííéÓ§k.gggEâ4tYYYøöÛo±xñbMa+V°°†žðññƒƒvîÜÉÌÐuï.oï¾ \»v®±'Ÿ”-gÆÉdläHYL‹ˆHϱ æÌ""€'”Ž„ . S§N8yò$¼¼¼´úZiiišdëØ±cˆ‹‹Ã¥K—®®®š$KýÓÅÅE«ñÖ0“&MBZZvîÜ©t(¤ ))ÀÖ­2!Û»WŽ#ëßÿ~ww¥#$"ª0ðöúö–.U:jááá#GŽÀ××·Á¶›ššªI¶Ô —ºeËÍÍMÓ}°OŸ>ðööF«V­쵉…5 ѪU«0{öl¤¦¦ÂÊÊJépH›²²€ðpYÄã?äý>}îñèÚU鉈4˜€edŽŽÀ/¿È/j¢zR'`@¿~ýê´ u u«Öƒc¶Z·n­I¶Ô WË–-ðЃ.¬ñÆo`òäÉ,¬a’““áææ†;v $$DépHW e‹˜zòç;w€Îe"6v,àçp|&)ˆcÀJKe·"ܽ{·\ËÖµk×îîîš1[ê„«ç©ÓºŠ küöÛo,¬a`\]]áåå…;w23&@h¨¼-[>,»)nØ|ü1àæv¿›âÀ€O…ˆH·ø­)»&pà.5uúŠddd 66GŽAll,Ž=ª)ý®®FøüóÏkº²@†neeeá믿ÆÒ¥KYX£‘6lvìØ¡t¤ _?y[¸øë¯ûåí¿ü°·FŒÉXH`m­tÄDdØ1 @öÿê+¥#¡F"##vvvؼy3\\\pôèQÍíüùóB uëÖðõõÕLhÌÒïÊbaÆk÷îÝ6l®]»we ]¾|¿¢âáÀ¥%0|¸LÆFŒÃˆˆ´À¸°ìlyõëÇ'žP:2`ÅÅÅ8}ú4Ž9‚C‡aõêÕ055EII áãã£I¸|}}Y CO°°Fã—ŸŸ,]ºÏ=÷œÒᾺsGVTܲEŽ+)¹_QqôhVT$¢eÜ ØÎ²ËAr2Àbª!!.^¼X¦eëøñãÈÍÍ… zöì‰Ã‡ãÍ7ßÄŒ3СC¥C¦‡<\XãÍ7ßÄ“O>ÉÂÔc=lذAéPÈdeÉJŠ›7;vܯ¨8v¬¼±¢"Õ“q'`ï¾+åž;§t$¤Çnݺ…èèh=zT3n+-- æææèÑ£|}}áãã???tíÚ¦¦¦P©Tøý÷ߦtøô·Š kÌ;—…5ŒÀÂ… ±`Áܹs‡kª‚9Oè–-²…ìöm S'™ˆ#+*š˜(%ãNÀ†•ÕV¯V:Ò¥¥¥8}ú4>ŒÃ‡ãСC¸xñ"LLLЩS'øúúÂÏÏ>>>èÕ«š4iRáv˜€é¬¬,|ûí·X¼x±¦°Æë¯¿ÎÂF$..>>>:™±ÒR *J&c¿þ*ǹº£FÉ„lÐ ÀÜ\é(‰ÈoD!€¸8ùÅIF+''GÅ¡C‡4 WFFlmm€É“'#00hÞ¼¹ÒáR-°°©õêÕ Íš5Cdd$0ª; o_yûäàäÉûs-_ØÙaa²e,4°±Q:b"ÒSÆÛvá‚ìF øû+ éHRR’&Ñ:xð þúë/ÃÝÝ}ûöE`` ‚‚‚гgO˜Õcn¶€)‡…5¨"aaa°µµÅúõë•…£«Wï'cÊ–°¡Ce26j§º!¢2Œ·,6VN¾Ø³§Ò‘–””” !!Ð$]‰‰‰033CÏž=„¹sç¢_¿~hÓ¦Mƒ¿~NNNƒo“*÷paåË—cÊ”),¬A€`áÂ…J‡AUÛ¶À«¯ÊÛÝ»À¶m2{ñEàùçåYÀcÙ2àßÿÚ·¿_Þ> €‰Œ„q¶€•”Í›‹œ˜Ó`¤¤¤àÀØ·oöïßøøx€——ˆþýû#((-[¶T8R¶€i kP}„„„ÀÁÁëÖ­dffb×®]ÈÎÎÆÓO?­lpd\JK˜Ù2¶y³—îâ"Ç‹<ú(PI•]"2|Æ™€ÅÇ=zÇŽ½{+ UâîÝ»ˆŒŒÔ$\ P©TèÙ³'ˆ ÿþ°··W4Î×_LJ£££æ± 6Àßßîîî䉞••¾üòK¸ºº*ªÁba jóçÏÇwß}‡W^y[¶lÁP\\ŒñãÇcãÆJ‡GÆìÔ)™ŒmÛ&{èX[Ç£GËÊŠü!"Ãgœ تUr`lf¦,ÄAz!==ûöíCDD"""púôi˜˜˜ W¯^0`ˆàà`½;é~ë­·°`Á‚­kŒ‡[}<\XcΜ9˜6m kP•”” ::¿ýö6mÚ„‹/ÂÌÌ ¥¥¥(--…™™f̘¯¾úJéP‰¤ädà·ßdBöçŸ@q1,[ÆFÚµS:B"ª'ãÌ>Žzõbò¥°üü|:tسgâââ „@¯^½‚  ÿþhÖ¬™Ò¡ViòäÉÕ&`xóÍ7u‘a«¨°Æo¿ýÆÂT+B¼üòËøâ‹/Èc°°°P\\¬YÏÔÔ”sü‘~qufΔ·¬, <غøÏd±îÝï'c¾¾¿‰ Žqf ±±@` ÒQ’’ÄÅÅaÏž=ˆˆˆÀ¡C‡ŸŸŽ;bÈ!xã70hР2]ù ——:vìˆ .TºNaa!ž|òIFexÔ…5-Z„äädÖ zQ©TÐ$`êä«"Ú®ŠJTgM›Þ/âQ\,«7oÛ¬] ÌŸ/“µ‘#eEÅAƒKK¥#&¢0¾.ˆÅŲ*Ñòå]kÝéÓ§±wï^DDD`ß¾}HOOG«V­0dÈ <C† ÑŒ“2d~ø!Þÿ}•[¦R©ÐµkW$$$(™þca Ò¦iÓ¦aýúõ›`ii‰>ú¯½öšŽ##ª§'dËØ¶mrL{Ó¦@Hˆl{ì1 ¾ã£…`ë‘–_vîÐ¥‹lã•õ—ššŠÝ»wc÷îÝØ¹s'nܸæÍ›cÀ€2d† ‚nݺ)fƒ»|ù2:tèPá/333ÌŸ?o¼ñ†‘é¯ k8::â•W^aa jpÙÙÙðòòÂ7Êt=T333Ã_|™3g*QIL”‰Ø¶m²•¬´0à~WÅÚ^Ð**’•¿þZž3Qƒ2¾lãFà‰'ääˆVVJGcðŠ‹‹…Ý»w#<<ÇŽƒJ¥‚¯¯/BCC1tèPøúúÂÔÔTéPµÎ××W3ŽíA*• W®\a‹Îß**¬1uêTX²ë iIll,+LÀLLL°fÍNAGF†œüyëVù3#CŽ{=Z&d}úT¿½{e+VÓ§k7f"#c|3þ%$È BL¾êìòåËX¾|9ÆŽ '''ôïßkÖ¬AŸ>}°~ýz¤¤¤àðáÃø×¿þ…€€£H¾ÙÕéá÷jbb??¿F™|:uªÆU‹‹‹ñã?¢wïÞ:t( ñÛo¿!!!Ó§OgòEZåããƒ>ú¨Â ÙKKKÑ´iS¢"Ò’æÍI“€Ÿ~îÜví‚‚€•+eÏY z÷n99tE¶mÌÍåï3fÈ1hééº{Dœñµ€Mœ('bþõW¥#1ÙÙÙØ»w¯¦[áÅ‹akk‹bøðá6l:uê¤t˜Š»uëÜÜÜPZZªyÌÔÔŸþ9^xá#kx‘‘‘0`ÂÃÃ1|øðJ×SÖX¼x1’’’0a¼þúë,¬A:'„À°aðÿþrãÁöìÙƒ!C†(‘Ž!ÇŠ©Çýõ—LÖBCeëXh¨¼îîÀõë÷Ÿkn8;6ÈdŽˆêÅø°.]€Ç>ø@éHôÚ™3gðûï¿#<<Dqq1zöì‰ :}ûöå\L"##5I×Å‹Ѽys :aaa AË–-•Óà=Û¶mƒ¥¥%îÞ½Û(ø/Z´sçέ°è†J¥‚ƒ ÂܹsñØcAŹcHÂÏÏýõàîÝ»ª›0Q¥¦Ê±^tÑ­”‰‰LÒ^xX¸hÒDûñ5" ÉÍÍEjj*RSS‘››‹ììl@QQ‘æw•JŸíÛáÔº5¢÷탽½=lllààà˜@B–››‹W_}£GFXXXžsóæMlݺÛ·oGDDrssáåå…qãÆ!,, AAAñÞõAnn.òòò‘‘ÜÜ\   ¹¹¹eÖóôôĶmÛðÈ# <<€œìÕÚÚºÌz666°°°Ð,kÞ¼9¬¬¬Ê­§´÷Þ{T1fÒÄÄO?ý4V¬X¡Ã¨ˆÊzðXLKKäåå!??_³Nzzºæ"Âk¯½†§Ÿ~333dffVùÍš5«´W€J¥*ÓÕ¶I“&°²²*󸵵5[…©R‰‰‰ˆ‰‰Q4†¶û÷Ã@.¡©“´¯¾¾ú ;?û ™­[k1:"Ãäïïww÷2é} X^^NŸ>K—.!11‰‰‰¸víqçΤ¦¦–ù[•žœ”/4mÚhݺ5ÜÝÝ57téÒ<òH…%ŒuåäÉ“˜0a.\¸€§Ÿ~«V­ªtÝ””lÚ´ ëׯÇþýûaii‰aÆ!44aaahmä_YYY¸~ý:nݺ…;wîàÞ½{¸wïRSS5¿«oÙÙÙÈÏÏGFF†NcT'c¶¶¶ppp€££c™›ú±-Z U«Vhݺuƒ·´ !ðÚk¯aéÒ¥Õ–›·³³Crr2¬8½U¡¤¤iiiš[ff&ÒÓÓ‘——‡ÜÜ\¤§§#77¹¹¹ÈÌÌDvv¶æ¢Zff&òòò““SæâZff¦¦+¡¡055E³fÍÈÿ=fff°±±••š5k†¦M›j†Z IDATŽÿ/ÌØÙÙÁÚÚVVV°³³C³fÍ`oo¯¹±Ë¸a[·n¦L™¢h û P@ý­¯‚œ³¨ºKµžð‹¶‚#2Pk×®-7פ^5}ܺu ÑÑш‹‹CBBpùòe”––ÂÌÌ ®®®pwwGÛ¶mñØc¡E‹š,õ­I“&šlþ“+))Ñ\Ý,--EFFrrr4-gê“ïääd\»v »wïFbb"RRRÈ+—žžžèÞ½;¼¼¼àçç­Ÿp !ðÕW_áµ×^Óœoß¾Bˆ2ݼ²³³±iÓ&üôÓOØ»w/,,,0bÄlذ¡¡¡Fsb\ZZŠëׯãÒ¥Kš¤ýúõëHJJBrr2®_¿Ž¬¬,Íú&&&å’tíÚŽŽŽ°µµE“&M`gg+++͉••š4iRfS³´´Ä;# h®v«[̤>qÌÏÏG^^žæDôÁßsrrÊ$„W®\)sÿÁJnM›6E›6màææWWW´iÓîîîèСÚ·oÖ­[×ø"BII ž{î9üðÃ5šë+33?þø#f̘Q£í“áËÈÈÀ­[·’’‚»wïâÖ­[¸wï^™+-- ééée®Š¨[ííí5ÉFóæÍacckkk´hÑ¢LRRQcnn[[[ò†‰‰‰¦JMÝêü 7ÞxŸ|òI™çUDý£2………ÈÉÉÑÜWó>/;;EEE&ÙÙÙÈËËCVV233‘““ƒ;wî”ù^HKK«ð»Díá„ÌÞÞvvvšßѲeK8;;ÃÙÙ-[¶,÷ýEÊSôºøŒ@n.,œå|©vvr¬—••,Qoc#oÖ °µ•¿7m 4mŠæffX`½rÑéʆd(ÚvñâEìܹ‡Btt4®\¹333tíÚݺuƒ——ºvíŠîÝ»ÃÃÃC‘®r¹¹¹8sæ N:…S§N!!!ñññ¸~ý:ÌÍÍѳgObÀ€xôÑGÑ\=‡F¸wïžzê)ìØ±£Ì‰6ÄÅÅ¡OŸ>ˆŠŠÂ7ß|ƒ7¢¨¨!!!xâ‰'0jÔ(ØØØ4X,ú&==ñññHHHÀùóçqáÂ\¾|—/_ÖœœØÙÙi’WWW¸¹¹¡uëÖšä¤U«VpttÔJ|999:ùüïÞ½‹Û·oãúõëHNNÆ7pãÆ Ü¼yS“|¦ÿ=y¦¥¥%Úµk§IÈ:uê///xyy•Ùo ñä“ObëÖ­µnYPWF$ÃTPP Ù®_¿Ž›7oâæÍ›¸{÷®f_»sçîÞ½[. °··‡““S¹ ª„ yóæU&=ºŸŸ&6vEÔedd”KxNz¼¥¤¤hºgªYZZÂÙÙ...pqq““œáêꊖ-[ÂÝÝnnnpssãÔ#Z¦nÓóŽIDT *•ªÂ0&`yyyøóÏ?±cÇ„‡‡ãâÅ‹pqqA`` „>}úDâpëÖ-DEEáðáȉ‰ÁÑ£GQ\\Œ   „„„ $$½{÷®óöÿüóOLš4 iiiå& µ°°À´iÓpæÌ>|}úôÁ3Ï<ƒÉ“'ÃÁÁ¡¾oM¯!pöìYœ8q'OžD||<âã㑘˜prrB§Nо}{tèÐA“\´oßžƒêÿ–’’‚‹/jZ/\¸€K—.áüùó¸wïÞÿoïÌ㢮öÿÿša†]ÙÇAd‘=QDnA¨¡¹ æRv53ÓL¯éµ¯zÓ²Ìnif×K·›Rš[šfîˆ%¢ˆ ˜¨,i.,²ÉÃ>ïßþæs˜™–ó|<æó™ó9çýù|Îû|Î묀ÂÇÇnnnغu«\žãñxÐÓÓǃT*m•e…B\¼xC‡ÕÈu1:FSSrrrðàÁdee!''¹¹¹ÈÎÎFNN=z„üü|.¼¾¾>lll`ooÏUÊe•t+++XZZÂÎÎŽëMÑÓÓÓâÕ1T¥¾¾žëµl.® QPPÀýŸŸŸüü|9·µµåÄXsa&™"‹µ*¦{:L€1½­ °††ÄÆÆbß¾}8räjjjäDÊСC{E‹¹D"Á¹sçäÄ¥³³3¦OŸŽéÓ§ÃÛÛ[¥x±nÝ:lذ|>_i„““¼½½±|ùrwå¥h•ââb\¾|YîSVVxyyqC@ŸyæøøøÀÎÎNÛ&÷h=z„[·náÆHIIÁ±cǸ¡Q|>fff‰D4h<==!‹aeekkk®niiÉæžtªªªpïÞ=üùçŸ\°ìûÇQ__àÉÊ*Ò²ÿmmm{EÙÌè4.ÀþøãüûßÿÆ®]»PVV†LŸ>‘‘‘jöÕ¸uëöîÝ‹}ûöáÞ½{ðññÁ›o¾‰Ù³g+],áÁƒ˜6mRRRÚúÅãñ““{{{u˜¯1JKKñÛo¿!..¿þú+233Áçóáé鉀€"00lµF5S^^ަ¦&˜šš"-- IIIœNOOÁÍÍ Ï?ÿ<ÂÂÂðÜsϱ –µDnn.ÒÓÓ‘žžŽ[·n!==™™™(,,ðD<‹D"…•àAƒÁÚÚZËWÀèMâþýû Ånn.7„ÞÚÚnnnðòò‚§§'<<<àááÛÌ` FoD#ŒˆpòäIlݺ±±±prrÂ[o½…3fôx¡ð4\¹r;vìÀ?ü¼úê«xçw0xð`.Ìþýû1oÞ<ÔÕÕ)âÕ|ýõט7ož:MïrqþüyÄÅÅáìÙ³HNN†ŽŽFމÐÐPŒ1þþþlbx7£¼¼W¯^Ebb"~ýõW\ºt MMMðóóCXXÂÂÂÌDrSTT„ëׯãæÍ›ÈÈÈÀíÛ·‘––Æ-ê`kkËÍ™• Åurr‚££#[îœÑ-¨««ãÄÙ½{÷žžŽŒŒ ¤¥¥qC^ÍÍÍáéé OOO¸»»ÃÇǾ¾¾}n9` FïC­ŒˆpìØ1¬[·ׯ_ÇØ±c±hÑ"Œ7ŽoFyy9bbbðïÿ÷îÝÃÌ™3±bŠ̘1·oßîP\:::3f Ž?®&k»Žššœ9s‡±cÇPRR‚!C†ÈUÜ»ÛÞWŒ¶©®®FBBâââ‡ÔÔTôë×&LÀäÉ“Þã6Ð6ùùùHIIArr2RRR’’ÂÍutpp€»»;×kàíí XXXhÙj£ó”––r éééÜßììlOò½ŸŸ|}}¹­­­–­VL€1½µ °„„¼ûví&Mš„uëÖá™gžy*c{;R©û÷ïÇêÕ«ñàÁƒv [ðù|91+s/‘Hºåó 8qâvïÞ'N ¶¶!!!˜2e &NœØç÷"ëmäääàðáÃøùçŸCCCŒ7³fÍBDDëkA]]®^½ŠóçÏ#)) )))ÈÍ͟χ««+|}}¹Šç°aÃØPOFŸ¢¬¬ ׯ_ç#’““q÷î]H¥RˆD"øúúbĈ†¿¿¯éíeŒÁè}t¹+..ÆßÿþwÄÄÄ <<6l€¯¯o—ÛWhjjÂîÝ»ñüX½z5&MšÄm8*Û÷¥ùÞP‰555(((@]]Þÿ}xzzjûR8îܹƒíÛ·cçÎ(,,Dxx8¢¢¢ðÒK/õ‰¹Œ'«.=z?þø#bccakk‹W_}o¼ñœµmžV¨ªªBbb".\¸€øøx\¹rµµµpqqA`` üüüàç燡C‡vù†Ú Fo ¢¢¿ÿþ;'È’’’p÷î]" £GFpp0FŽÙ#VRV` Fï£KØÏ?ÿŒ7Þxúúúزe ¢¢¢ºÌоHUU>øàlÙ²ؽ{7´m–ÊÈæþ}öÙgˆ‡ƒƒ^ýu¼öÚk=ê:]OVV¾ûî;lß¾ÙÙÙ ÁßÿþwŒ7NÛ¦©©TŠk×®áØ±cˆErr2¤R)¼¼¼‚Q£FaôèÑlOã)xôè€øøx¤¥¥ÏçÃÏÏááá˜0aüüüzÌT&ÀŒÞG—°††¬\¹[¶lÁ¼yóðù石źÔÔT¼öÚkÈÎÎÆ÷߈ˆm›Ô&R©Ä'Ÿ|‚7nàÅ_Ä’%KÚc^x Í •JqæÌlݺ'OžÄСC±jÕ*L™2¥×ä•ŠŠ œ9sÇŽÉ'PXXwwwDDD $$AAA½nŸ>£;QRR‚ .pûfffÂÆÆ/¾ø""""Þ­{˜™c0zÊHEÊÊÊhôèÑdllL?üðƒª§1:ˆD"¡¿þõ¯ÄãñhÆ Ú6G)G%777âóùôòË/Óï¿ÿ®m“=„””ŠŠŠ">ŸOtüøqm›Ôi*++)&&†BCCI__Ÿôõõ)<<œ¾üòKº{÷®¶Íc0ú4wîÜ¡/¾ø‚ÂÂÂ8ÿ £˜˜ª¬¬Ô¶y­Ø½{7u ZÆ`0zh÷îÝ­Ž«Ôô\RR‚°°0dggãêÕ«˜5kV×KDÀÐÐ111øæ›oðü«V­Ò¶Ir<|ø“&M„ 0tèPddd`ß¾}2dˆ¶Mcô† †üiiiðööFDD&OžÌ­ø×Ý!"œ?sæÌ.\kkkìÝ»ÅÅÅ8}ú4Þyç>;ßÁè.¸¸¸`éÒ¥8sæ ?~Œ={öÀ ,€æÌ™ƒóçϳ'ƒ¡qÚ`UUU Eee%âããáîî® »ú<óæÍÃþýû±iÓ&¬Y³FÛæ¶nÝ dff"..ûö탫««¶ÍbôPÜÜܸ…:ÒÒÒàá᯾úJÛf)¥¤¤6l€““BBB™™‰M›6!//{öìÁ”)S`ll¬m3 †LLL‰üyyyØ´i233WWW|òÉ'())Ѷ™ £Ð¦#"Ì™3UUU8wîÄb±¦ìbˆŒŒÄÁƒñé§ŸâÀZ³£ºº3gÎĻヒU«VáÆ Õš=ŒÞÅ /¼€›7obåÊ•X¶l^yåH$m›Å‘——‡%K–ÀÁÁ›6m´iÓžžŽÄÄDÌŸ?fffÚ6‘Á`t ÌŸ?‰‰‰HOOGdd$>ÿüs888`éÒ¥ÜÑ ƒ¡.Ú`›6mÂéÓ§qäÈ‘^½ùawf„ ظq#æÌ™ƒ?þøCãégggcäÈ‘ˆ‹‹Ã™3g°fÍèëëkÜFïF__ï¿ÿ>NŸ>ØØX!77W«6UUUaÕªUpqqÁáÇñÉ'Ÿ ++ Ÿ~ú) À`ôÜÝÝñé§Ÿ"++ ü1<WWW¬Y³¦[51Œ^†²IcÙÙÙ$ éàÁƒê›™ÖŒ²²2úüóÏé/ù YZZ’®®.éëë“­­-ùûûÓ¼yó¸°#FŒ #Ft(þ-[¶ dggG[¶lá~ÿàƒÈÖÖ¶Õo‹/&ccc@:::äííMiiiTSSCþþþ¤¯¯O4yò䮹 J¥4uêT3fŒÚÒPDNN¹¸¸ŸŸeeei4í–ô¥ü!³¡½ÏÓpõêU4hP—ÄÕÕüðCª««k3Ý™3gš?~»6väY¨Â‘#G(<<œúõëG:::dddDƒ¦I“&É…ëÌsí {÷î%±XÜ-Ë’Î ‘HèŸÿü'™››Ó AƒèôéÓKûiá`>ß1Ÿ×ÕÕ%[[[šõ=UU}·§¡ì™“——-^¼˜233µm¦Ö€’E8”zúœ9shìØ±j5JFJJ 0€lmm)::š00°Ó/–ö^Š~ËÎÎ&###‰DT]]ͯ««#¬˜››KFFF{ÔÕÕÑÈ‘#ÉßߟÊÊÊ4’¦2úZþ¦‚‚®(³©¡¡$ Pppp‡âTFw­4•––’¯¯/5Šêëë5–nSS­Y³†x<͘1ƒòóó5–vWÑ×üE‘UUUtéÒ%9r$ 7¶yŽT*¥¬¬,š2e   (M«±±‘œœœÈÌÌŒD"Q›vuôY´Ç¶mÛM›6222¨¡¡ içÎ$ åÂ>Ísí ݵ,é,yyy4uêTâóù´víZ’J¥jO³³Œù|Ç}¾ººšâããÉÃÃtttèäÉ“raµé»=•æ÷·±±‘îß¿O›7o& ëÐéntH€eddŸÏ§””µVVVF"‘ˆÌÍÍéþýûJÃ…‡‡wIz)<ˆˆ6lØ@hݺuܱÿû¿ÿ£?þ¸KìR…÷Þ{|}}5’ÖG}D"‘Hëо˜?Z¶ª)²©£-oÊèΕ¦ÜÜ\²³³ÓØv 4gδcǤÙÕôEiËŽÌÌL P():';;›P¿~ý”¦uáÂŠŠŠ¢©S§¥ïIu<‹ª¨¨hõ[XX˜Êñ¨ƒî\–< ß~û- š;w®ÚEXgóyy:êó/^$4|øpîX_óÝ®BÙ3IMM%¡PH†††ôàÁ-X¦]:$ÀÖ®]Kžžžj7ŠˆhãÆ€Ö®]«‘ô:[xÔÖÖ’‹‹ Qnn.]ºt‰‚‚‚¨±±Q]¦¶âæÍ›€ÒÓÓÕšNVV ‚V-BÚ€åõVlº{¥éرc$ );;[íi½ÿþûdllLçÏŸW{Zꢯú‹2;éëë«tNcc# ¥i­ZµŠvìØA;vì ´~ýz…áÔñ, ]½zµËâì*º{Yò4ÄÇÇ“P(¤>ø@­étF€1Ÿ—§£>_[[ÛÊçûšïvmå÷Þ{вeË4l•öéóööÖ˜35JåL™žžN...ryذa€ŒŒŒhÉ’%dooOzzzäééIûöíkGg ¢'•A4}út2dˆVÆ´º»»+}áw+V¬k Ò&½1 >œ•~ï¨M~~~djjJ:::äââ"'œsrr(**Š,,,ˆÏ瓹¹9 :´Í¸‡ BhذaZ~Jôä®X±B­i¤¤¤®®.:uJ­é¨›¾ê/Êì¸}ûv«Öí¶Î‘µž=Z©ÝC‡¥GQ^^ñx<¥eeGž…Œö®uôèÑ€lmmiÏž= ‡ç*z®eeeäááAÀ“¹/^^^$ ÉÑё֯_O>>>dllLFFFôÍ7ßpqu$?´¼ŸR©”Þÿ}²²²"¡PH³gϦšš•ïEwãøñ㤧§§Ö)`Ìçåé¨ÏWUU255åŽiËwe(óââbrww'àÉ<»œœš?>™™™QXXw}ööötòäIÚ¹s'7//22²Óq«r}Ê¤¤$@ÞÞÞrÇÛª¿´U†t¶L“qêÔ) âzæFŽÉMñ©­­%///@–––´ÿ~zî¹çH X,¦ãÇ+}vŠPY€ÕÕÕ:|øp‡è,ýû÷'ªèµ|ȲïiiiT__O™™™äëëKZõâúè#µöäääJHHP[厈Ê*Aƒ¦]»vQmm­ÂðÕÕÕ4fÌzûí·»Äæ®DV0æää¨%~Ù”'N¨%~MÒWý¥¥¹¹¹dnnNÔÐРÒ9ÕÕÕ”@þþþ¤§§G?ÿüs«s&Nœ(W6&&&š:uj«°yåÚµkôúë¯Ç#´páBî·ö˜*ß•S”Z†355%TUUÅU„ Ÿî‚µÌ‰'¸kR`ÌçŸÐŸ—U¢­­­iêÔ©­†•jÛwÛóeÏ ++‹x<9::Ñ“ùp–––r ‰t6nUhë\eù©­óU)C:S¦õë׫÷ÈõÐ5_ÙRÕò°=T`ùùù€Î;ס:‹¬‚xùòe•ÏQåË–®®®Âsµ`I¥ÒVJ¹9ãÆ£;wÒíÛ·IWW—\\\ÚÝ/¦«yûí·)$$Dmñ×ÖÖ’®®.8p@mit–?Úvøk×®Qpp0™™™‘ŽŽN«°< —^z‰ø|>OÆv7ò JfffdffFEEEOmsWràÀÒÓÓS«¯ÙÚÚÒ_|¡¶ø5E_õE6ïÚµ‹ÐêÕ«U>‡èÉ*À@ëa&uuuddd$W‰“}LMM[Íéè̳è,ÑÑѪÿ?:ëó-Ѷï¶ç;m]GHH ¤¤$Š‹‹k5îiân¶Î•-"çïïÏk¯þ¢JÒ™2M–Vó<ÜÔÔÔn™ÖÞ5*C™ã£ÖÖÖ000@nnnËŸÔÂĉ‡n3ÜäÉ“;oII ÀÒÒR……ÜïÍ),,„•••ÂøvîÜ @€W_}žžžX°`îÞ½‹­[·vÈ®§%++ b±XmñÀßß/^T[å¶™={6°}ûvÔÔÔ´ú}àÀ8rä>|ˆ—_~yyyxã7Z…[¾|9Ö­[‡òòr¬[·N­6w”„„@___miÌ›7;vì@cc£ÚÒÐÌ_þÇ+¯¼‚É“'cãÆ¸pá‚Êç9;;>|(w<>>aaa ' —Ügòäɨ¨¨@BB‚\xu<‹ÐÐP…Ç_{í5@uuµÊq= ÊòCsŒŒŒ r÷«§R__ÿþ÷¿˜3g޶M‘ƒùüÿè¬Ï·DÛ¾û4¾óÊ+¯öíÛ‡Ÿ~ú Ó§O—û][~)+›ß‡öê/ê²ÕÌÌ äÒ¬ªª˜››?uü*£H­;–æÌ™Ó!…×YÊËËÉÁÁŒŒŒ(--Mi8pßmûn{¾ÓÖu”––’†ó¤x°IDATÙÛÛ“H$j5ÿûiâneçÖÔÔ‡‡ÑǹãíÕ_T)C:S¦É‚i¾Êå¹sç?¾Ís;s”ù‹ÂX¶oßNm.“ٕܼy“ÉÒÒ’¢££)''‡©¤¤„bcc)22’Œÿg´’\\\LMMM”––Fnnnd``@ׯ_—Këúõë$ ÉÆÆ†Nž¯ ÚôÝö|§½ë˜2e P8]¥³qwä™TVV’T*¥ºº:ºví…††’¾¾~«<Ð^ýE•2¤3ešlÄqãÆq« ‘ŽŽŽÜ_­°²²2²´´¤~ø¡C‰< •••´yóf ¦~ýúŸÏ'@@ÎÎÎôÒK/Ñ—_~Iû÷ï'î888p•J>ŸOb±˜tuuI(ÒèÑ£•.$‘ššJS§N%kkkÒÕÕ¥þýûÓĉ[MÄŒ‹‹£‹År=O™™™r¶¸¹¹É-ý©&L˜@¯¾úªZÓKºººÜ¾Ú¦7åU÷{á…ÈÚÚš‹ÃÚÚš^xá¹0ß~û-™˜˜ mÞ¼™ÛÃËÓÓ“***h̘1dccC|>ŸÌÌÌhâĉôðáC*..æöÐhÞú6~üøVÇ´ÅÑ£GIWW—âââ4–æ‘#GH ÐØ±c©°°Pcév5}Å_Ö­[Gb±˜;ÏÆÆ†fÍšÕÊÆéÓ§rtt¤¨¨(îÅßüÃçó©_¿~4f̹¥·Oœ8ÁÙ-‰hÆ Üoëׯ'{{{.‘HDÛ¶mëð³Ptmоoݺ•&L˜@$}}}ÒÕÕ%{{{zùå—¹û¥è¹FGG“§§'wÌÙÙ™[Nöý³Ï>ã¾»»»sù¿½ü ,}üñÇ$‹IGG‡(·:[O   €ÂÃÃI ÐÑ£GÕž^góù–4÷ùÐÐP¹s¬­­iìØ±]rOÙ¨è»*¾ÛE¾Óò½íàà@û÷ïouîO?ýDöØw6î¶®/::šL†††¤§§Ç-,"ÈÉɉæÍ›§°ñ¥½ú‹2[‰žè”ΖiDD'Ož¤áÇ“@ ¡PHAAA ÷“åͤ¤$rssãŽyyyÉ-âÑÊïÿÿØŠ¯¾ú ›6mBFF¢ Ý>Ÿ¦¦&m›¢6~ûí7¼øâ‹ÈÌÌ„ƒƒƒFÒ\³f ¾øâ œ:u ÁÁÁISô…üÑÛøí·ß0~üx,_¾|ðFÓNIIAdd$*++±iÓ&Ìž=<O£6hæ/ŒæôÅü@Dˆ‰‰ÁŠ+`nnŽŸ~ú C‡U{º{öìÁ¬Y³4>W®/>cCSðx<ìÞ½3gΔ;Þj ,€–.]ªv㺩TªmÔFII ^ýu,[¶Lcâ Ö¯_éÓ§#<<ß}÷ÆÒU½9ô6¶oߎqãÆaÖ¬YZYÄ×ש©©˜5kæÎ‹€€œ9sFãvhæ/Œæô¥üpúôi<û쳘?>fÏž7nhD|i›¾ôŒŒî€R¦§§‡ï¿ÿ111رc‡&mb4£©© ³fÍ‚™™V¯^­Ñ´y<þûßÿbÅŠ˜;w.-Z„††ÚÀè;444`áÂ…xã7ðÞ{ïá?ÿùÖzžLLLðå—_"%%666GPP:Ä** F/C*•âСC ÄØ±c!‰pýúu|ñÅ066Ö¶y £¢T€@`` ¾úê+¼õÖ[øå—_4e“Ê”••ÁÃÃû.‹£E‹º–¦¦&¼ù書rå :¡P¨qø|>>üðC:t?üð†ŽË—/kÜŽÎÐÛóGoâÒ¥KÀîÝ»qäȬ[·®[ û2dŽ;†ÄÄDØØØ ** ƒÆúõë‘••¥móºæ/Œæô…üðçŸbýúõpuuETTD".]º„_~ù>>>Ú6Oíô…gÌ`t[T™@¶|ùrÒÓÓ£}ûö©4áŒñô444ÐÌ™3ÉØØXc›b·Gff&=ÿüóÄçóiÞ¼yÝnÃ^FÏ£¨¨ˆæÎK<BCCé?þжIm’™™I+W®${{{âñx4zôhЉ‰¡ÊÊJm›Æ`0T ²²’bbbhôèÑÄãñÈÞÞžV®\Ù-Êž§Y„ƒÁ`tO dŽ6{Àd|öÙgxï½÷0kÖ,|úé§=z3Åž@qq1Æ'NàôéÓ ѶI€Áƒãìٳؽ{7Ž?777|ôÑG(++Ó¶iŒFii)×ò|úôiìÛ·qqqpuuÕ¶im2xð`lܸYYY8vììíí±páBØÙÙaÚ´iøþûïQTT¤m3 F3ŠŠŠðÝwßaêÔ©°³³ÃÂ… !‹qâÄ deeaãÆÝ¾ìa0½ ¥« *⫯¾Â»ï¾‹ˆˆÄÄÄhvÇè>BRR^~ùeèèèàСCÝvòoee%þùÏbÛ¶mhjj¢E‹°téRX[[kÛ4F7¦°°›7oFtt4tuu±xñb,_¾¼Gϳ(//ÇO?ý„£G"..‰˜0a^|ñÅnëà Foæúõë8~ü8Ž;†«W¯B("<<ˆŒŒ„™™™¶Ml…¶VAd0êCÙ*ˆ`ÀÿÇÿþõ/Œ?¾K í«ÔÔÔ`ãÆøä“O0fÌìÚµ«GÜŠŠ DGGcóæÍ¨ªªÂ´iÓ0þ|Œ1BÛ¦1º/^Ä·ß~‹ýû÷ÃÔÔË–-Ã[o½m›Ö¥ÔÖÖâüùó8zô(Ž?Žû÷ïC$!$$ÁÁÁ†§§g·˜ßÆ`ôˆiii8þ<.\¸€sçÎáÑ£GprrBDDÆhÛÔ6aŒÁè}t™ž ‘{çw°gÏL™2_~ù% ÐeÆö5Μ9ƒ… ¢  }ô/^Üã*h555صk¾ùæ$''ÃÝÝóçÏǬY³X¯X¥°°;wîÄöíÛ‘‘‘Á-íüÊ+¯ÀÐÐPÛæi„Û·o#66ñññ¸xñ"?~ KKKq¢lèСÐÕÕÕ¶© F¡±±ׯ_GBB'ºŠ‹‹aii‰Q£FaôèÑ;v¬Ü=&ÀŒÞG— 02á——‡E‹aÅŠ°´´|jcû ×®]ÃÚµkqâÄ L:[¶lH$Ò¶YOÍ7ðí·ßbÏž=¨¬¬ÄsÏ=‡É“'cÒ¤I°µµÕ¶y 5’ŸŸŸþ?ýôâããabb‚™3gbþüù}bU±¶hÙJÜÜ\ 2¾¾¾ðó󃯯/¼½½¡§§§m“ ­ÓÐЀ[·n!99)))HNNFjj*jkk1`À„„„p¢ËÃãÇ5^6‡ 0£÷¡<éùˆŽŽÆ§Ÿ~ ‰D‚%K–`É’%°²²z*ƒ{3×®]ÃúõëqôèQ >~ø!^xám›ÕåÔÔÔàĉ8|ø0Ž?ŽŠŠ aÒ¤Iˆˆˆ€›››¶Mdt™™™8~ü8:„K—.ÁÔÔãÇGdd$Æ×í‡ýh“û÷ïãÒ¥K\Å2%%000€üüüàçç///xxxÀÂÂBÛ&3j£´´ééé¸u떜ت¯¯‡™™† ÆùĈ#àèè¨m“»&ÀŒÞ‡Ú˜Œªª*lÛ¶ Ÿþ9*++…Å‹#  +¢ïñÔÕÕáÀضm’’’àïïµk×"""BÛ¦i„úúzœ={‡Æ/¿ü‚‚‚ˆÅb„††"44aaa¬w¬‡——‡¸¸8œ={qqqÈÍÍ… &Nœˆ)S¦à¹çžƒ¾¾¾¶Íì‘îÞ½+'È’““¹•Fmmmáéé OOOxyyÁÝÝ^^^¬Á‹Ñ£(**ÂíÛ·9±•‘‘´´4äççÌÍ͹Þ`Ù_—Ý»¥ L€1½µ 0555Ø·o¶mÛ†äädøúúböìÙˆŠŠ‚½½}W&Õ#¸rå öîÝ‹={ö ´´‘‘‘X´hF¥mÓ´!55•«À'$$ ªª ^^^>|8FŒÀÀ@xxx@GGGÛæöiššš––†¤¤$$%%áÒ¥KHOO‡±±1BBBðüóÏ#,, >>>½¾r¤Mrss‘žžŽÛ·o#-- éééHKKCqq1 ÿþpssƒ³³3œœœ¸¿NNN°³³Ó²õŒ¾H^^îÝ»‡?ÿüSîoff¦\¾mÞ àáá^1¿30Æ`ô>4&Àš“””„˜˜>>;v,ÂÃÃ1|øð½uNNâããqòäIÄÆÆ¢¨¨®®®ˆŠŠÂŒ3àíí­m{D„ŒŒ \¾|™diiihhh€@ €——|||¸ÏàÁƒ!‹Y%REššš••…;wî 55·oßFjj*ÒÒÒP[[ ===xzzr=‘pwwg=\=ŒÜÜ\¹ ±ìÿû÷ïsC½@OO¶¶¶ppp€H$‚½½={{{ˆD"XYYÁÎήG—Ñ Õ©¬¬D^^ŠŠŠ››‹ÜÜ\deeáÑ£GÈÉÉAvv6òóóÑÐÐàIåÂÆÆƒ ’ëy•‰®¾8ò¥³0Æ`ô>´*ÀšÓØØˆ¤¤$ÄÆÆâÔ©SHNNǃ··7‚‚‚0bÄøùùÁÕÕµ[.Í\YY‰[·náòåËHLLDbb"rssallŒÐÐP„‡‡#<<...Ú6µWQ__Ï͸yó&RSSqëÖ-dgg àè踺ºÂÅÅ...pppÀ€ú\å±²²999ÈÊÊÂÝ»wq÷î]ܹswïÞÅýû÷Q__‹ÅrbÖÇÇîîîlW/§¾¾^®BýèÑ#dggËõhäç磩©‰;G ÀÊÊ ¶¶¶°¶¶†••lll`cc+++X[[ÃÒÒ°°°èûöÊÊÊPZZŠÒÒR<~ü………(**BAA PTT„ÂÂBäç磨¨ˆë9]]]ØØØpâ\$A,C$aÀ€0`ìííYyÑE0Æ`ô>ºkÉãÇ‘˜˜ˆ+W®àâÅ‹¸zõ*ª««¡¯¯wwwxxxÀÇÇNNNppp€££#ìììÔÚÛQ[[‹¬¬,deeááÇÈÌÌÄ­[·––†‡D"FމÀÀ@âÙgŸe/!-PZZÊ ‹æ"ãÞ½{(,,䙘˜`À€\ ¿X,†­­-,--¹OÿþýÑ¿…B-^‘r$ Š‹‹Q\\ŒÇsŸüü|®››‹œœTVVrçY[[sÂÔÉɉ§ƒf•d†RšššŸŸ/W9/**âzGŠŠŠ¸ß‹ŠŠ8Q/ƒÇãÁÜÜœdÍ…YóïFFF044„™™Œ! all SSS…ÂnëêF"‘@"‘ ¢¢•••¨©©AUUÊË˹ÿ›‹«ÒÒÒVßKKK[Åk`` P<ÛÚÚÂÊÊŠëñ´´´„­­-ò­A˜c0zÝV€µ¤±±‘›`~óæM¤§§ãæÍ›xðà7äAOO € úõë'÷055å^²e›ëëëQ]] àÉB!µµµ¨¨¨@II JJJP\\Œ’’äååÉ Ï111áÆ«{zzÂÇÇ^^^‹Å¾3ŒŽRQQÁ é¼¼<äää 77—kíÏÏÏÇãÇ!•JåÎ344”cFFF055…@ à*†²ÿ'slš/µ.«x6§¬¬Lî¥ZWW‰DàÉ ¢²üØò‰DÂåOÙœ |>–––°³³k%.íìì ‹áààSSÓ.½¯ †"JKKQ\\¬T (eee¨ªªj%ÞZbnnΉ1333àÊy}}}qáx< !ú¢‘‘‘ÒÆ²æqµ¤ººZ©Íß/2d>/{ß·š¥,®ÆÆF®±¤¬¬ 555H$\8eÀÈÈH©¨UöÝÊÊŠ»Œî‡L€ýøãÚ6…Á`tÓ¦MëLR©yyyxðàW©.((à”ìSWWǽøš¿ðtuu¹ah …011‘o²¿AƒÁÁÁlß>€¬WIö‘ žââbTWWC"‘ ¼¼µµµ¨®®–ûx2ܯ±±‘‹¯y¥J†‰‰‰ÜÚæùÑÈÈfff­þ …œlþéׯú÷ﯻÃ`¨Ÿ¦¦&®§G"‘p~&‘HPSSƒÒÒR¹!(//‡T*Emm-jjjäÊ{‰D‚ºº:.Þæ´l iN["«-qÆçó[ ™@”½o€ÿ5ÊbóóÌÌÌ`hh¡P …B¹žACCC˜˜˜È502z2Æ`0z=Z€1 ƒÁ`0 FO‡-Ç`0 ƒÁ`0‚ 0ƒÁ`0 ƒÁÐL€1 ƒÁ`0 ††ÐpCÛF0 ƒÁ`0 F_àÿØ?lµâ;)IEND®B`‚CipUX-RPC-3.4.0.9/doc/tecdoc000755001750001750 011432067074 16106 5ustar00ckuelkerckuelker000000000000CipUX-RPC-3.4.0.9/doc/tecdoc/tecdoc_preamble.tex000444001750001750 1477311432067074 22131 0ustar00ckuelkerckuelker000000000000% +=========================================================================+ % || Preamble || % +=========================================================================+ \usepackage{epsfig} \usepackage{alltt} %\usepackage[scaled=.85]{helvet} \usepackage{relsize} % used for CipUX def \usepackage{courier} % make bold typwriter possible \usepackage{verbatim} \usepackage{color} %\usepackage{fontspec} %\setmainfont[SmallCapsFont={* Caps}]{Latin Modern Roman} %\setsansfont{Latin Modern Sans} %\setmonofont[SmallCapsFont={Latin Modern Mono Caps}]{Latin Modern Mono Light} \usepackage{fancyvrb} \usepackage{slashbox} \usepackage{mdwlist} %\usepackage[dvipsnames]{xcolor} \usepackage[dvipsnames,table]{xcolor} %\usepackage{colortbl} %\usepackage{keyval} \usepackage{listings} \lstset{% general command to set parameter(s) language=Perl, % language frame=single, % frame syle numbers=left, % numberstyle=\tiny, % stepnumber=1, % numbersep=5pt, % fancyvrb=true, % %index=[1][identifier], %indexstyle=[1]\color{red}, emph={\$login,\$ticket,\$dummy,\$epoche,\$header\_hr,\$param\_hr,\$cmdres_ref,\$server,\$pay\_hr,\$https\_url,\$answer\_hr},emphstyle=\color{Blue}, %moredelim=[l][\color{red}]{\$}, %vars moredelim=*[s][\color{LimeGreen}]{"}{"}, % eval string moredelim=*[s][\color{OliveGreen}]{'}{'}, %normal string %moredelim=[s][\color{Goldenrod}]{->}{(}, %methods morekeywords={new,call}, backgroundcolor=\color{GreenYellow},% basicstyle=\small\ttfamily, % print whole listing small %stringstyle=\color{ForrestGreen}\itshape, stringstyle=\itshape, keywordstyle=\color{Thistle}\bfseries\itshape, % underlined bold black keywords identifierstyle=\color{CornflowerBlue}, % nothing happens commentstyle=\color{BurntOrange}, % white comments stringstyle=\ttfamily, % typewriter type for strings showstringspaces=false} % no special string spaces \newcommand{\bc}{\begin{center}} \newcommand{\ec}{\end{center}} \newcommand{\bi}{\begin{itemize}} \newcommand{\ei}{\end{itemize}} \newcommand{\bd}[1]{\begin{dinglist}{#1}} \newcommand{\ed}{\end{dinglist}} \newcommand{\bt}{\begin{tabular}} \newcommand{\et}{\end{tabular}} \newcommand{\ttt}[1]{\texttt{#1}} \def\CipUX{{\texttt{\larger\textbf{CipUX}}}} \def\CAT{{\texttt{\larger \textbf{CAT}}}} \usepackage{fancyvrb} % VERBATIM: %\DefineVerbatimEnvironment% %{filecontent}{Verbatim} %{frame=single,numbers=left,numbersep=2mm,labelposition=topline, %rulecolor=\color{black},defineactive={\color{black}\footnotesize},fillcolor=\color{white}} % filecontent \DefineVerbatimEnvironment% {filecontent}{Verbatim} {frame=single,numbers=left,numbersep=2mm,labelposition=topline, rulecolor=\color{black},defineactive={\color{black}\footnotesize}} % perlcode \DefineVerbatimEnvironment% {perlcode}{Verbatim} {frame=single,numbers=left,numbersep=2mm,labelposition=topline, rulecolor=\color{black},defineactive={\color{black}\footnotesize}} % perlcodefile \CustomVerbatimCommand{\perlcodefile}{VerbatimInput}{frame=single,numbers=left,numbersep=2mm,labelposition=topline,rulecolor=\color{black},defineactive={\color{black}\footnotesize}} %SHELL \usepackage{verbatim,framed} % NEW COMMAND shell \newenvironment{shell}[1][Black]% {% \definecolor{shadecolor}{named}{#1}% \color{White}% % supress ugly vertical space which is inserted by % environment above and below text: \topsep=0ex\relax % start shaded and verbatim: \shaded \footnotesize\verbatim \~\$ }% {% \endverbatim \endshaded }% % NEW COMMAND out \newenvironment{out}[1][Black]% {% \definecolor{shadecolor}{named}{#1}% \color{White}% % supress ugly vertical space which is inserted by % environment above and below text: \topsep=0ex\relax % start shaded and verbatim: \shaded \footnotesize\verbatim }% {% \endverbatim \endshaded }% %paramlist for list of parameters \newenvironment{paramlist}[1]% {\begin{list}{}{\settowidth{\labelwidth}{\textbf{#1}} \setlength{\leftmargin}{\labelwidth} \addtolength{\leftmargin}{\labelsep} \renewcommand{\makelabel}[1]{\textbf{\hfill##1}}}}% {\end{list}} %API \newcommand{\api}[7]{% \begin{minipage}{15cm} \rowcolors[\hline]{1}{blue!25}{gray!25} \arrayrulecolor{black!75!gray} \begin{tabular}{|ll|} \textbf{call:} & #1 \\ \textbf{cmd:} & #2 \\ \textbf{login parameter:} & #3 \\ \textbf{ticket parameter:} & #4 \\ \textbf{param\_hr:} & #5 \\ \textbf{cmdres\_r type:} & #6 \\ \textbf{cmdres\_r value:} & #7 \\ \end{tabular} \bigskip \end{minipage} } \usepackage{pifont,calc} % Lst=List W=white|B=balck, n=normal|s=sanserif, \newenvironment{LstWn}{\begin{dingautolist}{172}}{\end{dingautolist}} \newenvironment{LstWs}{\begin{dingautolist}{192}}{\end{dingautolist}} \newenvironment{LstBn}{\begin{dingautolist}{182}}{\end{dingautolist}} \newenvironment{LstBs}{\begin{dingautolist}{202}}{\end{dingautolist}} \newcounter{local} % Lst=List W=white|B=balck, n=normal|s=sanserif, N=number \newcommand{\LstWnN}[1]{\setcounter{local}{171+#1}\ding{\value{local}}} \newcommand{\LstWsN}[1]{\setcounter{local}{191+#1}\ding{\value{local}}} \newcommand{\LstBnN}[1]{\setcounter{local}{181+#1}\ding{\value{local}}} \newcommand{\LstBsN}[1]{\setcounter{local}{201+#1}\ding{\value{local}}} % Roman numbers \makeatletter \newcommand{\rmnum}[1]{\romannumeral #1} \newcommand{\Rmnum}[1]{\expandafter\@slowromancap\romannumeral #1@} \makeatother % no indentation \setlength{\parindent}{0pt} \setlength{\parskip}{2ex} \usepackage{tikz} %\usetikzlibrary{arrows,shapes,decorations.pathmorphing,backgrounds,placments,fit} %\usetikzlibrary{arrows,shapes,decorations.pathmorphing,placments,fit} \usetikzlibrary{arrows,backgrounds,fit} %\usepackage{caption} \usepackage[hypcap=true,justification=raggedright,font=footnotesize,single linecheck=off]{caption} % should be last package \usepackage{hyperref} \hypersetup{% backref,% %pdfpagemode=FullScreen,% presentation mode per default pagecolor=\blue,% link color pdftitle={The CipUX XML-RPC Server and Client},% pdfauthor={Christian K\"ulker},% pdfsubject={technical document}, pdfkeywords={CipUX, XML-RPC, server, client}, bookmarks=true,% PDF bookmarks %pageanchors=false,% needed by hyperindex %ihyperindex=true,% page number in index are links colorlinks=true} %--------------------------------------------------------------------- %\pagestyle{myfootings} \markboth{{\CipUX}: Technical Documentation} {{\CipUX}: Technical Documentation} CipUX-RPC-3.4.0.9/doc/tecdoc/rpc.tex000444001750001750 20727011432067074 17621 0ustar00ckuelkerckuelker000000000000% scope - Bereich % socket Modus - % payload - % scalar value - % TTL - Time To Live % dispatch list % +=====================================================================+ % || Content || % +=====================================================================+ \section{Preface} This document supports developers of XML-RPC clients and describes practical parts of the XML-RPC server calls and responses in detail while other parts like installing are skipped. Others might find to read this document useful too. In the beginning a section shows different parts of the software package and the configuration of the main server part. It also includes a part of how using the server in a secure way. The following section introduce briefly the session concept and a short section thereafter draw a picture representing the categories of XML-RPC server calls. For the impatient two example calls and responses are displayed in a section by itself. One example is a didactically sum call while the other is simple real life call from the task scope. The next two sections describe the construction of the client call and server response more specific. The most important section of this document specify all XML-RPC server scopes with their sub-commands in detail. The focus of this paper lies on the task scope. Afterwards the toolbox section introduce programs and hints for testing and debugging of XML-RPC clients. The last section is a summary of already developed administration tools. Those Free Open Source Software projects are provided there as a starting point for gathering more practical informations. Thanks to Jean-Charles Siegel and Jochen Breuer for a contribution in this section. \section{Parts of \CipUX{} XML-RPC server} The CipUX-RPC-3.4.0.9.tar.gz package is a standard CPAN package. The latest package can be downloaded from \href{http://release.cipux.org}{http://release.cipux.org}. This overview will not explain how a CPAN package works, but what this package provides and installs in the file system. It contains basically two approaches (A) + (B) which might (in the future) merge into one. (A) the \CipUX{} XML-RPC sever and (B) security wrapper for stunnel4. Additional it contains (C) some utility tools. You need at least (A) to have a plain \CipUX{} XML-RPC server. If you also install (B) you can use a secure connection over the stunnel4 wrapper. And finally if you also install and use (C) you can test some aspects of the XML-RPC server and use cipux\_rpc\_list as a convenient method to list all XML-RPC calls and task scope sub-commands. Scripts in (D) are provided in the source code upstream tar release for the purpose of study. Some of those scripts are referenced, explained or even included in this document. \begin{verbatim} (A) /usr/share/perl5/CipUX/RPC.pm /usr/share/perl5/CipUX/RPC/Server.pm /usr/share/perl5/CipUX/RPC/Server/Daemon.pm /usr/sbin/cipux_rpcd /etc/init.d/cipux-rpcd /usr/share/cipux/etc/cipux-rpc.ini \end{verbatim} \begin{verbatim} (B) /etc/cipux/stunnel/readme.txt /etc/cipux/stunnel-cert.conf /etc/cipux/stunnel.conf /etc/init.d/cipux-rpcdr /usr/sbin/cipux_mkcertkey /usr/sbin/cipux_rpcdr \end{verbatim} \begin{verbatim} (C) /usr/bin/cipux_rpc_list /usr/share/perl5/CipUX/RPC/Test/Client.pm /usr/sbin/cipux_rpc_test_client /usr/bin/cipux_rpc_test_repetition \end{verbatim} \begin{verbatim} (D) doc/example/bin/expl_rpc_ping doc/example/bin/expl_rpc_session doc/example/bin/expl_rpc_task_create_destroy doc/example/bin/expl_rpc_task_list doc/example/bin/expl_rpc_task_member doc/example/bin/expl_rpc_task_sum \end{verbatim} \section{Configuration of the server} The \CipUX{} XML-RPC server is a Perl script called \verb|cipux_rpcd| with its attached modules \verb|CipUX::RPC*|. The server script can be found (depending on your installation) for example under \verb|/usr/sbin/cipux_rpcd|. The default server configuration is for example at \verb|/usr/share/cipux/etc/cipux-rpc.ini|. This default configuration can be overwritten by other files. This document will not explain the configuration space here. Just to give an idea, that it is not enough to parse a single file, overwriting configuration can be found for example in: \verb|/etc/cipux/cipux-rpc.ini| or \verb|~/.cipux/cipux-rpc.conf|. Valid configuration directives - which are essential to know for programming XML-RPC clients - in this file are: \begin{filecontent} xml_rpc_port = 8001 xml_rpc_address = localhost xml_rpc_proto = tcp intern_admin_group = admin \end{filecontent} Some of this default values are changeable today, some are planned to made changeable in the future. This set of values describes the plain \CipUX{} XML-RPC server on port 8001. It is fine to use this port in socket mode on localhost. However you should not use this port for remote communication over network. The SSL encryption is handled by the stunnel4 wrapper for now, but might be integrated into the default server. If you plan to communicate over the network use the port defined in the stunnel4 configuration for CipUX. Per default this port is 8000. %\subsection{Secure connection to the XML-RPC Server} The \CipUX\ XML-RPC server in version up to 3.4.0.7 do handle \emph{http} requests but not \emph{https} requests. To make the connection secure you have to use a different software. One possibility is to use stunnel4 which is the default solution for secure connections to the \CipUX\ XML-RPC server so far. There is not much to say about this software. The Perl script \verb|cipux_rpcdr| is a wrapper to stunnel4 with its start script \verb|/etc/init.d/cipux-rpcdr| and a separate configuration file \verb|stunnel.conf| and certificates. The default is to accept https requests on port 8000. For more information please refer to the stunnel4 documentation. \newpage \section{Communication via session} The communication with the XML-RPC server is straight forward. The following describes a simplified session. %\begin{verbatim} %0 client ---> ping ---> server %1 client <--- answer <--- server % %2 client ---> login ---> server %3 client <--- answer(session) <--- server % %4 client ---> list(session) ---> server %5 client <--- answer <--- server % %6 client ---> logout(session) ---> server %7 client <--- answer <--- server %\end{verbatim} \begin{center} \begin{tikzpicture} [server/.style={circle,draw=blue!50,fill=blue!20,thick}, client/.style={rectangle,draw=black!50,fill=black!20,thick}, c2s/.style={->,shorten <=1pt,>=latex,thick}, s2c/.style={->,shorten >=1pt,>=latex,thick}, c2sw/.style={auto,rotate=-5}, s2cw/.style={auto,rotate=5,swap}, ] %\node (c0) at ( 0,0) [client] {client}; %\node (s1) at (12,1) [server] {server} edge [s2c] node [s2cw] {answer} (c0); \node (c2) at ( 0,2) [client] {client}; \node (s3) at (12,3) [server] {server} edge [s2c] node [s2cw] {answer} (c2);% 8 \node (c4) at ( 0,4) [client] {client} edge [c2s] node [c2sw] {logout(session)} (s3);% 7 \node (s5) at (12,5) [server] {server} edge [s2c] node [s2cw] {answer} (c4);% 6 \node (c6) at ( 0,6) [client] {client} edge [c2s] node [c2sw] {list(session)} (s5);% 5 \node (s7) at (12,7) [server] {server} edge [s2c] node [s2cw] {answer(session)} (c6);% 4 \node (c8) at ( 0,8) [client] {client} edge [c2s] node [c2sw] {login} (s7);% 3 \node (s9) at (12,9) [server] {server} edge [s2c] node [s2cw] {answer} (c8);% 2 \node (c10) at ( 0,10) [client] {client} edge [c2s] node [c2sw] {ping} (s9);% 1 \end{tikzpicture} \end{center} \newpage If you run longer sessions with your clients - which is not advisable for web application through their fragile nature (lot of timeout traps) - you should take care of the session key. Here is an example of communication which renews the session key. The ttl call is not mandatory and can be used to examine how long as session can live. %\begin{verbatim} % 0 client ---> ping ---> server % 1 client <--- answer <--- server % % 2 client ---> login ---> server % 3 client <--- answer(session1) <--- server % % 4 client ---> ttl(session1) ---> server % 5 client <--- answer <--- server % % 6 client ---> someting(session1) ---> server % 7 client <--- answer <--- server % % 8 client ---> session(session1) ---> server % 9 client <--- answer(session2) <--- server % %10 client ---> someting(session2) ---> server %11 client <--- answer <--- server %12 client ---> logout(session2) ---> server %13 client <--- answer <--- server %\end{verbatim} \begin{center} \begin{tikzpicture} [server/.style={circle,draw=blue!50,fill=blue!20,thick}, client/.style={rectangle,draw=black!50,fill=black!20,thick}, c2s/.style={->,shorten <=1pt,>=latex,thick}, s2c/.style={->,shorten >=1pt,>=latex,thick}, c2sw/.style={auto,rotate=-5}, s2cw/.style={auto,rotate=5,swap}, ] \node (c0) at ( 0,0) [client] {client}; \node (s1) at (12,1) [server] {server} edge [s2c] node [s2cw] {answer} (c0);% 14 \node (c2) at ( 0,2) [client] {client} edge [c2s] node [c2sw] {logout(session2)} (s1);% 13 \node (s3) at (12,3) [server] {server} edge [s2c] node [s2cw] {answer} (c2);% 12 \node (c4) at ( 0,4) [client] {client} edge [c2s] node [c2sw] {something(session2)} (s3);% 11 \node (s5) at (12,5) [server] {server} edge [s2c] node [s2cw] {answer(session2)} (c4);% 10 \node (c6) at ( 0,6) [client] {client} edge [c2s] node [c2sw] {session(session1)} (s5);% 09 \node (s7) at (12,7) [server] {server} edge [s2c] node [s2cw] {answer} (c6);% 08 \node (c8) at ( 0,8) [client] {client} edge [c2s] node [c2sw] {something(session1)} (s7);% 07 \node (s9) at (12,9) [server] {server} edge [s2c] node [s2cw] {answer} (c8);% 06 \node (c10) at ( 0,10) [client] {client} edge [c2s] node [c2sw] {ttl(session1)} (s9);% 05 \node (s11) at (12,11) [server] {server} edge [s2c] node [s2cw] {answer(session1)} (c10);% 04 \node (c12) at ( 0,12) [client] {client} edge [c2s] node [c2sw] {login} (s11);% 03 \node (s13) at (12,13) [server] {server} edge [s2c] node [s2cw] {answer} (c12);% 02 \node (c14) at ( 0,14) [client] {client} edge [c2s] node [c2sw] {ping} (s13);% 01 \end{tikzpicture} \end{center} \section{Categories of RPC calls} The above calls - like \verb|list| - are of course simplified. The list call will be introduced in detail later. To answer the question which calls are needed, one should know that they can be categorized as follows: \begin{LstBs} \item simple calls (mostly for testing): \verb|sum| \item availability calls: \verb|ping| \item authentication calls: \verb|login, logout| \item session management calls: \verb|session, ttl| \item task calls with sub-commands: call: \verb|task| (sub-commands: \verb|cipux_task_*|) %\item rpc server management calls: \verb|rpc_info, rpc_intern| \end{LstBs} If you write a \CipUX{} RPC client, you need in most situations only the calls from \LstBsN{2}, \LstBsN{3}, \LstBsN{4} and \LstBsN{5}. Occasionally in the development phase you might also need \LstBsN{1}. Especially \LstBsN{1} might be a good starting point, if you are new in the RPC development. \section{Example call and answer} Before more details are presented and explained this section will just provide two examples \verb|sum| and \verb|task| of a client call and the server response with discrete values for the impatient. The examples are taken from the \verb|cipux_rpc_test_client| script. \subsection{Sum call example} \label{full_sum_call_example} Here is the client code for \lstinline|'sum'| call with its \verb|sum| command. A simple call and command that do not need a ticket but it need two parameters: %\begin{lstlisting}[caption=Descrete sum call example,emph={cipux_task_sum},emphstyle={\color{red}\underbar}] %\begin{Verbatim}[commandchars=\\\[\]] \begin{perlcode}[commandchars=\\\[\],label={Discrete sum call example}] my $https_url = "https://localhost:8001/RPC2"; my $server = Frontier::Client->new( url => $https_url ); my $pay_hr = { # hash ref of payload header_hr => { # header is part 1 of payload cipux_version => '3.4.0.0', client_name => 'cipux_rpc_test_client', client_version => '3.4.0.0', rpc_version => '2.0', client_key => 'dummy', client_cred => 'dummy', gmt_time => '1195349030', }, # some important key-value pairs login => 'dummy', ticket => 'dummy', \fbox[cmd => 'sum'], param_hr => { # second part of payload: parameter 'summand2' => '4', 'summand1' => '3', }, }; my $answer_hr = $server->\fbox[call( 'sum'], $pay_hr ); \end{perlcode} %\end{lstlisting} And here is the value of \lstinline|$answer_hr| which comes from the server displayed in the Data::Dumper format: \begin{out} \$answer_hr = { 'header_hr' => { 'cipux_version' => '3.4.0.0', 'server_key' => '', 'server_cred' => '', 'gmt_time' => '1195349220', 'server_version' => '3.4.0.0', 'server_name' => 'cipux_rpcd', 'rpc_version' => '2.0' }, 'cmd' => 'sum', 'ticket' => 'dummy', 'login' => 'dummy', 'status' => 'TRUE', 'type' => 'aref' 'cmdres_r' => [ '7' ], }; \end{out} \subsection{Task call example} \label{full_task_call_example} The task call sub-command \verb|cipux_task_list_student_accounts| is a real world CipUX::Task XML-RPC example. It is is also a good example for a simple parameterless call. One part, which is also different from the last call is, that a login and ticket is required. \begin{perlcode}[commandchars=\\\[\],label=Discrete list call example] my $https_url = "https://localhost:8001/RPC2"; my $server = Frontier::Client->new( url => $https_url ); my $pay_hr = { header_hr => { cipux_version => '3.4.0.0', client_name => 'cipux_rpc_test_client', client_version => '3.4.0.0', rpc_version => '2.0', client_key => 'dummy', client_cred => 'dummy', gmt_time => '1195349030', }, login => 'cipadmin', ticket => '48df73accd8530af69f97cf1c847f29e', cmd =>\fbox['cipux_task_list_student_accounts'], param_hr => { }, }; my $answer_hr = $server->call( \fbox['task'], $pay_hr); \end{perlcode} Of course this was a static \lstinline|$pay_hr| call. And the ticket is not valid any more. Therefore this example shows \emph{what} data is transfered or programed not \emph{how} it should be transfered. Hard coded tickets are not at all a solution. And here is the value of \$answer\_hr again in Data::Dumper format: \begin{out} \$answer_hr = { 'msg' => '', 'problem' => '0', 'ltarget' => 'memberUid', 'cmd' => 'cipux_task_list_student_accounts', 'cmdres_r' => { 'students' => { 'cn' => [ 'students' ], 'memberUid' => [ 'bilbo', 'frodo', 'mytest', ] } }, 'status' => 'TRUE', 'header_hr' => { 'server_cred' => '', 'server_key' => '', 'cipux_version' => '3.4.0.0', 'gmt_time' => '1260715448', 'server_version' => '3.4.0.0', 'rpc_version' => '2.0', 'server_name' => 'cipux_rpcd' }, 'type' => 'HASH', 'ticket' => 'dc43ee1170d9514ad7f762f561b4382b', 'login' => 'cipadmin' }; \end{out} \section{Construction of the client call} In general a XML-RPC call can be made by a simple evocation: \bigskip \lstinline{my $answer_hr = $server->call('RPC scope',$pay_hr);} \bigskip This line is made from different parts. \Rmnum{1}. The \lstinline{$answer_hr} which contains the server response explained in section \ref{server_response} on page \pageref{server_response}. \Rmnum{2}. The \lstinline{$server} is a Perl object. See the example client in section \ref{expl_rpc_ping} on page \pageref{expl_rpc_ping} how to get that. \Rmnum{3}. The Frontier::Client key word \lstinline{call}, which is a subroutine in Frontier::Client. You probably do not need to look that up. \Rmnum{4}. The last part represents the parameters to \lstinline{call}. Every call contains two parameters first the \lstinline{'RPC scope'} name (ping, login, task, ...) and second a hash reference \lstinline{$pay_hr} to the payload. \subsection{RPC scope} The RPC scope is the name of the subroutine (\lstinline{sub}) of CipUX::RPC Perl module. The following table shows different RPC scopes and their constrains. \bigskip \rowcolors[\hline]{2}{blue!25}{gray!25} \arrayrulecolor{black!75!gray} \begin{tabular}{|l|l|l|l|}\hline \textbf{RPC scope} & \textbf{require login} &% \textbf{ticket check} & \textbf{ticket renew} \\\hline ping & no & no & no \\ version & no & no & no \\ sum & no & no & no \\ login & yes & yes & no \\ logout & yes & yes & no \\ session & yes & yes & yes \\ ttl & yes & yes & no \\ task & yes (*) & yes (*) & no \\\hline \end{tabular} \bigskip (*) Only for the task \verb|cipux_task_sum| the value is "no". \subsection{Payload} The payload can be assigned through a reference to a hash. This reference are constructed out of 5 mandatory parts (2-6): % ??? \caption{General payload hash} \begin{perlcode}[commandchars=\\\[\],label=General payload hash] \fbox[$pay_hr] = { header_hr => $HEADER_HR, login => $login, ticket => $ticket, cmd => 'sub-command name', param_hr => $param_hr, }; \end{perlcode} \begin{paramlist}{param\_hr} \item[header\_hr] The key \verb|header_hr| demands a reference to a hash as its value: the so called "header". The header as of protocol version 2.0 remains static during the communication, the count of keys are fixed. The values are also more or less fixed. The \verb|gmt_time| value received from the server however can change. The server uses only the \verb|client_name|. Therefore the client should provide and use a registered name. A not registered client might be rejected. Since there is no prove of validity of a client name, the server do not trust a client just because it is registered. Therefore rejecting or not rejecting a registered client is not a matter of security it is just a matter of convenience for the client developer. This behavior might change in the future, but it is foreseen that this has to go along with using other (not jet used) header fields, like \verb|client_key| and \verb|client_cred|. \label{client_header} Since the header remains basically the same, this document will not print the header over and over again. It will use the following header taken from \verb|cipux_rpc_test_client| whenever the hash reference \lstinline|$HEADER_HR| occur. \begin{perlcode}[commandchars=\\\[\],label=General header hash] \fbox[$HEADER_HR] = { cipux_version => '3.4.0.0', client_name => '/usr/bin/cipux_rpc_test_client', client_version => '3.4.0.0', # can be choosen rpc_version => '2.0', client_key => $dummy, # $dummy not used client_cred => $dummy, gmt_time => $epoche, }; \end{perlcode} \item[login] is the user id (uid) of the logged in user. You may not use an empty string. Also keep in mind that this do not refer to numerical user id (uidNumber). \item[ticket] is the valid session ticked of the logged-in user. You may not use an empty string, But some calls need a string. So if the call do not need a ticket ``dummy'' or ``test'' is a good choice. \item[cmd] name of the sub-command. Some RPC scopes do have only one command. Like the RPC scope ``\verb|ping|'' which will have the \lstinline|cmd| ``\verb|ping|''. Other RPC scopes like ``\verb|task|'' to have several hundreds of \lstinline|cmd|s. You may not use an empty string here. \item[param\_hr] is a reference to a hash. It can be a empty reference to a hash but not a reference to an array. The expected content depends on the \verb|cmd| value. And this is related to the call subroutine of CipUX::RPC and secondly to the task subroutine of CipUX::Task. Later parts of this document will provide more hints on how to determine which parameter keys are possible. \end{paramlist} \section{The server response} \label{server_response} This section describes what will be send back from the server and how to interpret this. The client might have implemented this call to the server. \bigskip \lstinline{my $answer_hr = $server->call('RPC scope',$pay_hr);} \bigskip In Perl the answer \lstinline{$answer_hr} of the server is a reference to a hash and is made out of seven parts. \begin{out} \$answer_hr = { header_hr => $server_header_hr, # 1 login => $login, # 2 ticket => $ticket, # 3 cmd => 'sub-command name', # 4 status => 'TRUE|FALSE', # 5 type => 'href|aref|string', # 6 cmdres_r => $cmdres_r, # 7 }; \end{out} \begin{paramlist}{server\_version} \item[header\_hr] is a key in the payload. The value of this key is a hash reference to a ``header'', which (again) remains more or less the same during a session. \begin{minipage}{10cm} \begin{out} \$server_header_hr = { cipux_version => '3.4.0.0', server_name => 'cipux_rpcd', # server name server_version => '3.4.0.0', # not fixed rpc_version => '2.0', server_key => $dummy, # reserved server_cred => $dummy, # reserved gmt_time => time(), # server time }; \end{out} \end{minipage} \medskip See the following subsection for details about the header keys. This answer-header is basically the same as the header which is used in the client call in section \ref{client_header}. \begin{paramlist}{server\_version} \item[cipux\_version] is a key with a scalar value. The \CipUX{} version is given from the server. However only the first 3 digits are significant. The fourth digit will probably not change. \item[server\_name] contains a scalar value with the name of the server. Its value is constant and as long nobody else develops a different server it will \verb|cipux_rpcd|. \item[server\_version] has a scalar value of the server version. All four digits of this response are significant. The server will increase this version, if the server was updated and restarted. \item[rpc\_version] represents a scalar value of the \CipUX{} XML-RPC protocol version. This is the version number of the RPC calls. The number is unlikely to be changed within \CipUX{} 3.4.0.y but might change if needed in \CipUX{} 3.4.x.y. Your client should only accept connections up to a discrete version number. \item[server\_key] Not used now. \item[server\_cred] Not used now. \item[gmt\_time] The time is set by the server. \end{paramlist} \item[login] is the uid of the logged in user \item[ticket] is the more or less valid ticked of the logged in user, which was used by the user request. This field will never provide a new or renewed ticket. you have to use the session call for this. See section \ref{session} on page \pageref{session}. \item[cmd] name of the sub-command \item[status] is key with a boolean scalar value. The status can be \verb|TRUE| or \verb|FALSE|. \item[type] returned data type: href, aref, string \item[cmdres\_r] is key which name derived from command (\verb|cmd|) result (\verb|res|) reference (\verb|_r|). The value is a reference to an array or hash which is constructed like this \lstinline|$cmdres_r = ['a','b','c']| or \lstinline|$cmdres_r = { ttl=> 20 }| % or \lstinline|$cmdres_r = 'OK'| \item[ltarget] is a key which can be used to automatically parse the output of the \verb|cmdres_r| key value. The value of \verb|ltarget| \emph{can} be a LDAP attribute like uidNumber if the target of the task scope sub-command is only one attribute. This feature is considered to be experimental. \end{paramlist} At the moment some calls return other undocumented fields and there might be more fields added in the future. The fields which are documented here are considered to be a part of the version 2.0 of the protocol. You should use them. You could also use the undocumented fields. However they might vanish or change with out warning. If you are missing some fields or if you want that a field will be officially, you can join cipux-devel and make an existing field stable or develop a new field, which can be introduced in the next protocol version 2.2. \section{Calls and responses in detail} The CipUX::RPC call ``ping'' and CipUX::RPC ``sum'' can be used to test the RPC server without authentication. \subsection{ping} \api{ping}{ping}{no}{no}{empty}{hash reference}{empty} The aim of this function is to see if the server is up. The return value will always be the status ``\verb|TRUE|''. No value in \lstinline|$cmdres_r|. So if you are getting a status other then ``\verb|TRUE|'' the server is up but has a problem. If you get no answer the server is not up. Sorry due to the nature of logic it was not possible to implement an answer of ``\verb|DOWN|'' from the server if the server is down. \begin{out} \$status => 'TRUE', $cmdres_r = { } \end{out} This short program tests if the local server is up and running or not. However it does not tell you if the stunnel4 wrapper is working or not. %\begin{perlcode} %\begin{perlcode}[commandchars=\\\[\],label={CipUX RPC test ping client}] \label{expl_rpc_ping} \perlcodefile[label={expl\_rpc\_ping}]{../example/bin/expl_rpc_ping} Example Answer: \begin{out} \$answer_hr = { 'msg' => '', 'ltarget' => 'NULL', 'cmd' => 'ping', 'cmdres_r' => {}, 'status' => 'TRUE', 'header_hr' => { 'server_cred' => '', 'server_key' => '', 'cipux_version' => '3.4.0.0', 'gmt_time' => '1260436207', 'server_version' => '3.4.0.0', 'rpc_version' => '2.0', 'server_name' => 'cipux_rpcd' }, 'type' => 'href', 'ticket' => 'dummy', 'login' => 'dummy' } \end{out} \subsection{version} \api{version}{version}{no}{no}{empty}{hashref}{cipux\_version, server\_version, rpc\_version} The aim of the call is to test the version of the CipUX::RPC server (without logging in) to be able do decide if a login might be possible or not. An other thing is that you can test with this function, if you are able to parse the returned hash reference. The relevant part of the answer looks as follows. \begin{out} 'cmdres_r' => { 'cipux_version' => '3.4.0.0', 'server_version' => '3.4.0.0', 'rpc_version' => '2.0' }, \end{out} \subsection{sum} \api{sum}{sum}{no}{no}{summand1, summand2}{array reference}{a scalar value} The CipUX::RPC server provides two sum functions: The first is CipUX::RPC \verb|sum| and the second is and CipUX::Task \verb|cipux_task_sum|. This section is about the simple CipUX::RPC sum function. The aim of this function is to test, if you can send arguments via hash reference and if you can parse the returned array reference. \begin{perlcode} param_hr => { summand1 => 3, summand2 => 4, }, \end{perlcode} The call will return an array reference with a scalar value. See section \ref{full_sum_call_example} on page \pageref{full_sum_call_example} for a full example. \begin{out} 'cmdres_r' => [ '7' ], \end{out} \subsection{cipux\_task\_sum} \api{task}{cipux\_task\_sum}{no}{no}{summand1, summand2}{array reference}{a scalar value} The \verb|cipux_task_sum| is similar to the \verb|sum| call. Except that it is internally invoked in the task section of the XML-RPC server. You have already noticed the difference between the \lstinline|cmd| and \lstinline|call| section above. Therefore rather then testing your client this call can be used to test the rpc server. It is summarized here for the sake of completeness. The call will return an array reference with a scalar value. See section \ref{full_sum_call_example} on page \pageref{full_sum_call_example} for a similar example with the \lstinline|'sum'| call with its \verb|sum| sub command. \subsection{login} \api{login}{login}{yes}{no}{password}{hash reference}{ttl, login, ticket} The login call request 2 parameters. One parameter is given as payload and one parameter will be supplied inside \lstinline|param_hr|. \begin{perlcode}[commandchars=\\\[\],label={login call payload}] \$pay_hr = { param_hr => { \fbox[password => '**********'] }, cmd => 'login', header_hr => { cipux_version => '3.4.0.0', client_key => '', client_cred => '', gmt_time => '1196466596', client_name => 'cipux_rpc_test_client', client_version => '3.4.0.0', rpc_version => '2.0' }, ticket => 'dummy', \fbox[login => 'cipadmin'] }; \end{perlcode} An positive answer should contain: \begin{out} \$answer_hr = { 'msg' => '', 'cmd' => 'login', 'cmdres_r' => { 'ttl' => '20', 'ticket' => '1559cc7c463af4a5a28586e931fbf744', 'login' => 'cipadmin' }, 'status' => 'TRUE', 'header_hr' => { 'cipux_version' => '3.4.0.0', 'server_key' => '', 'server_cred' => '', 'gmt_time' => '1196466106', 'server_version' => '3.4.0.0', 'server_name' => 'cipux_rpcd', 'rpc_version' => '2.0' }, 'type' => 'href', 'ticket' => 'dummy', 'login' => 'cipadmin' }; \end{out} Be aware that you have to grab the \lstinline|cmdres_r| ticket not the payload ticket! The example script \verb|expl_rpc_session| contains a \verb|login| call. \subsection{logout} \api{logout}{logout}{yes}{yes}{empty}{hash reference}{empty} There is nothing much to tell about the \verb|logout| call. If it is successfully issued it returns ``\verb|TRUE|'' as its status back. The example script \verb|expl_rpc_session| contains a \verb|logout| call. \begin{out} 'status' => 'TRUE', \end{out} \subsection{ttl} \api{ttl}{ttl}{yes}{yes}{empty}{hash reference}{ttl} The relevant part of the answer: \begin{out} 'cmdres_r' => {'ttl' => '20'}, \end{out} See section \ref{call_ttl_as_ttl} on page \pageref{call_ttl_as_ttl} for an example. \subsection{session} \label{session} \api{session}{session}{yes}{yes}{empty}{hash reference}{ticket or empty} The session call can be used in two ways with the same syntax. First it can be used to check if the session was still valid. Second the call gives you a new ticket in the \lstinline|cmdres_r|, so you can use the call to extent the session. The best is to uses both ways together. If the session is not valid, you should examine the \lstinline|msg| payload field for more information. In that case \lstinline|cmdres_r| is empty. \begin{out} \$answer_hr = \{ 'msg' => 'The ticket check was not successful. This could have several reasons: a timeout, logout, ... Please log in again. is_ticket_bad: (ticket is bad: time login mismatch)', 'cmd' => 'session', 'cmdres_r' => {}, 'status' => 'FALSE', 'header_hr' => { 'cipux_version' => '3.4.0.0', 'server_key' => '', 'server_cred' => '', 'gmt_time' => '1196466106', 'server_version' => '3.4.0.0', 'server_name' => 'cipux_rpcd', 'rpc_version' => '2.0' }, 'type' => 'href', 'ticket' => '1559cc7c463af4a5a28586e931fbf744', 'login' => 'cipadmin' }; \end{out} If the session is still a valid session a new ticket will be given via the \lstinline|cmdres_r| reference. In this case a hash reference with the key \lstinline|ticket| will be returned. \begin{out} 'cmdres_r' => { ticket => '285c9699e3f664fbfce5d04e6d0b98e0' }, \end{out} You can grab this for example with this lines of Perl code. \begin{perlcode} my $new_ticket = undef; if ( exists $answer_hr->{cmdres_r}->{ticket} and defined $answer_hr->{cmdres_r}->{ticket} and $answer_hr->{cmdres_r}->{ticket} ) { $new_ticket = $answer_hr->{cmdres_r}->{ticket}; } \end{perlcode} The old session ticket, which you send to the server will also given back via the \lstinline|ticket| key in the payload of the answer. To make the distinction clear you could grab this with this lines of Perl code. \begin{perlcode} my $old_ticket = undef; if ( exists $answer_hr->{ticket} and defined $answer_hr->{ticket} and $answer_hr->{ticket} ) { $old_ticket = $answer_hr->{ticket}; } \end{perlcode} Sometimes things get wrong. One real user scenario might be that the user waited too long and the session expired or might get wrong because of other reasons. In this case the answer from the server would look like this. Non important keys were omitted. \begin{out} 'msg' => 'Your ticket is not valid! This can have serveral reasons: (1) the\ ticket expired. (2) an error in programming (3) you never logged in', 'cmdres_r' => {}, 'status' => 'FALSE', 'ticket' => 'test', 'login' => 'test' \end{out} So you can expect to get a status of \verb|FALSE| and an empty \lstinline|cmdres_r|. The \verb|msg| field might give a hint what is wrong. For now it refers to 3 problems. This might changed in the future to be more precise. This message might also be translated into other languages. For an example see the script \verb|expl_rpc_session|, it contains a \verb|session| call. \subsection{task} \api{task}{name of CipUX::Task command}{yes}{yes}{depends}{hash reference}{depends} The RPC scope ``task'' is the most used part of the \CipUX{} XML-RPC server calls. Because the \verb|task| scope has a lot of sub-commands we will start with a simple example. We assume that you have some users in the role ``student'' or ``students'' on your system. To list those you have to use the sub-command \verb|cipux_task_list_student_accounts|. Just to show you the expected output you can use the CipUX::Task layer directly as root. \begin{shell} cipux_task_client -t cipux_task_list_student_accounts \end{shell} If you have students on you system you might get a similar output like this: \begin{out} students bilbo frodo mytest \end{out} Here ``students'' is the role and the members of this role are: ``bilbo'', ``frodo'' and ``mytest''. Section \ref{full_task_call_example} on page \pageref{full_task_call_example} contains a full example what parameter are used and what the expected output looks like if you code this by hand. If you re-program this call by hand it can get longly homework. Programming ping, login, task and then logout will take a long time. To make this shorter in Perl you can use the helper module \verb|CipUX::RPC::Client|. If you use an other language then Perl have a look at the source code and comments of the \lstinline|sub extract_data_for_tpl { ... }| subroutine of \verb|CipUX::RPC::Client|. This was originally written for \CAT{} to give it a helper routine to parse the output of an XML-RPC answer. However you probably should implement a similar or even better routine in your application. Here comes a full fledged \verb|CipUX::RPC::Client| example with the XML-RPC scopes ping, login, task (\verb|cipux_task_list_student_accounts|) and of course logout. \perlcodefile[label={expl\_rpc\_task\_list}]{../example/bin/expl_rpc_task_list} If you use this sample program you will get output like this. \begin{out} expl_rpc_task_list$ Enter login: cipadmin expl_rpc_task_list$ Enter password: Students on the system: bilbo frodo mytest \end{out} One short word on the server response. The server response depends on the sub command and the data structure which come from the storage layer. This is the data which the \verb|cipux_task_list_student_accounts| sub-command returns. The most relevant parts of this answer are \lstinline|'ltarget'| and \lstinline|'cmdres_r'| \begin{out} \$answer_hr = { 'msg' => '', 'ltarget' => 'memberUid', 'cmdres_r' => { 'students' => { 'cn' => [ 'students' ], 'memberUid' => [ 'bilbo', 'frodo', 'mytest', ] } }, 'cmd' => 'cipux_task_list_student_accounts', 'status' => 'TRUE', 'login' => 'cipadmin', 'problem' => 0, 'header_hr' => { 'cipux_version' => '3.4.0.0', 'server_key' => '', 'server_cred' => '', 'gmt_time' => 1260715448, 'server_version' => '3.4.0.0', 'server_name' => 'cipux_rpcd', 'rpc_version' => '2.0' }, 'type' => 'HASH', 'ticket' => '240bf8a03ed86a8f5b762f9cccdce73f' }; \end{out} The expression \begin{perlcode} my $d_hr = $rpc->extract_data_for_tpl( { answer_hr => $a_hr } ); \end{perlcode} would convert this to the following quite similar output. \begin{out} \$d_hr ={ 'tpl_data_ar' => [ { 'cn' => 'students', 'memberUid' => 'bilbo, frodo, mytest' } ] }; \end{out} You can use this directly. And the key \verb|ltarget| with its value \lstinline|'memberUid'| will be provided by the server response. The trick the former mentioned subroutine does is parse every output and transform this to a unified format. The additionally parameter \verb|use_ltarget| \begin{perlcode} my $d_hr = $rpc->extract_data_for_tpl( { answer_hr => $a_hr, use_ltarget => 1, } ); \end{perlcode} will produce a very different data structure. \begin{out} \$d_hr = { 'ltarget' => 'memberUid', 'tpl_data_ar' => [ { 'memberUid' => 'bilbo' }, { 'memberUid' => 'frodo' }, { 'memberUid' => 'mytest' }, ] }; \end{out} This look somewhat bigger, but is automatically processable. \subsubsection{Taxonomy} This section deals with logic behind task scope sub-command names. You can derive the mandatory parameter(s) from this logic. \rowcolors[\hline]{2}{blue!25}{gray!25} \arrayrulecolor{black!75!gray} \begin{tabular}{l|l|l} \textbf{sub-command schematic} & \textbf{param\_hr} & \textbf{return scope} \\\hline $*$\_add\_(M)\_to\_(G) & object=(G), value=(M)& \\ $*$\_change\_(X)\_(L) & object=(X), value=(L) & \\ $*$\_create\_(X) & object=(X) [1] & \\ $*$\_deregister\_(X) & object=(X) & \\ $*$\_destroy\_(X) & object=(X) & \\ $*$\_disable\_(X) & object=(X) & \\ $*$\_enable\_(X) & object=(X) & \\ $*$\_list\_(A)s & &all objects \\ $*$\_list\_(M)\_of\_(G) & object=(X) &all (M) \\ $*$\_obtain\_(X)\_(L) & object=(X) &one attribute \\ $*$\_register\_(A) & object=(X) & \\ $*$\_remove\_(M)\_from\_(G) & object=(G), value=(M)& \\ $*$\_retrieve\_all\_(A)\_(L1)\_..\_(Ln) & object=(X) &all attributes\\ %$*$\_retrieve\_from\_(A)_all\_(L1)\_..\_(Ln) & object=(X) $*$\_search\_all\_(L) & &all attributes\\ $*$\_sum & [2] &one sum \\ \end{tabular} \begin{footnotesize} \begin{verbatim} *: cipux_task [1]: require sometimes some other parameter depending on the object require no additional parameter for *_account or *_share [2]: only for testing (A): object is coded in task name (G): group object, line *_account or *_share (L): LDAP attribute codes as English name (memberUid -> member) (M): member, client (Y): member, client (variable) (X): ID, which is a variable ID to the object name (variable) \end{verbatim} \end{footnotesize} %disfemist %eufemist \subsubsection{Object call parameter} \label{object_call_parameter} The \lstinline|'object'| parameter is an abstract \lstinline|$param_hr| hash key to define the name of the thing the sub-command is operating on. As you learn in the RPC \verb|task| scope sub-command taxonomy, not every sub-command do need an object, but some do. If the sub-command needs an object it can be provided like this: \begin{perlcode}[commandchars=\\\[\],label={Object parameter}] my $param_hr = { \fbox[object] => $some_object_scalar, }; \end{perlcode} The example script \verb|expl_rpc_task_create_destroy| plays with this object parameter. It creates a student account and delete it thereafter. \perlcodefile[label={expl\_rpc\_task\_create\_destroy}]{../example/bin/expl_rpc_task_create_destroy} The script requires a parameter, the name of the account which should be created. In this case \verb|mytestobj1| was provided as an object. \begin{shell} perl expl_rpc_task_create_destroy mytestobj1 expl_rpc_create_destroy$ Enter login: cipadmin expl_rpc_create_destroy$ Enter password: Students on the system: bilbo frodo mytest Students on the system: bilbo frodo mytest mytestobj1 Students on the system: bilbo frodo mytest \end{shell} \subsubsection{Value call parameter} \label{value_call_parameter} The \lstinline|'value'| parameter is an abstract \lstinline|$param_hr| hash key to define the data for a given \lstinline|'object'|. Like \verb|--object frbeutlin| or \verb|-o frbeutlin| and \verb|-x value=Frodo| giving on the command line the value of \verb|-x| are most likely referring to the \emph{first name} of the account \verb|frbeutlin|. In this terms the meaning of the abstract \verb|value| is determined by the sub-command name. As you learned in the RPC scope \verb|task| taxonomy about sub-commands, not every sub command do need a \verb|value|, but some do. If the sub-command needs a \verb|value| it can be provided like this on the command line: \begin{shell} cipux_task_client -t cipux_task_add_member_to_role_account \ -o tutor \ -x value=frbeutlin \end{shell} Keep in mind that the role \emph{tutor} might be different on your system. The same sub-command looks with its parameters in code like: \begin{perlcode}[commandchars=\\\[\],label={Abstract value parameter hash}] 'param_hr' => { 'object' => 'tutor' \fbox['value'] => 'frbeutlin', }, \end{perlcode} \subsubsection{Other call parameters} In most cases there are performed additional calculations on the abstract \verb|object| and \verb|value| parameters. Therefore it is the best practice to provide those parameters as abstract hash key. This was introduced in section \ref{object_call_parameter} and \ref{value_call_parameter} at page \pageref{object_call_parameter} and \pageref{value_call_parameter}. %The pro is that you do not have to care when you write clients which use task %of the form \verb|cipux_task_add_(Y)_to_(X)| where as Y might be [client, %member] and X might be a basic \verb|user_account| oder \verb|group_share| or %netgroup. This new ``value'' parameter can be used by all which uses %CipUX::Object \verb|change_object_attribute_action|. This should work for tasks %like: % %\begin{verbatim} % - cipux_task_add_(Y)_to_(X) % - cipux_task_remove_(Y)_from_(X) %\end{verbatim} It is of course possible to provide other parameters too, not just \verb|object| and \verb|value|. \begin{perlcode}[commandchars=\\\[\],label={Create call with other parameters}] my $pay_hr = { header_hr => $header_hr, login => $login, ticket => $ticket, cmd => 'cipux_task_create_student', param_hr => { object => 'bibeutin', \fbox[cipuxFirstname] => 'Bilbo', # cipux.schema \fbox[cipuxLastname] => 'Beutlin', # cipux.schema \fbox[userPassword] => $new_password, # core.schema \fbox[homeDirectory] => '/home/cipux0/bibeutlin', # nis.schema }, }; \end{perlcode} However, this example would work only on systems where those LDAP attributes are defined. In this example \verb|cipuxFirstname| and \verb|cipuxLastname| is defined in the cipux.schema. If you use those LDAP attributes directly you make your application dependent on the existence and usage of those schemata. If possible try to avoid the direct use of LDAP attributes. Unfortunately \CipUX{} is not providing more abstract attributes. It would be a nice feature to have a dispatch list for this in some future version to avoid using LDAP attributes directly. Foreseen attributes might be \verb|firstname|, \verb|lastname|, \verb|mailaddress|, ... But there is limited solution for this problems in the current version, which will be introduced now. The above problem could be avoided if you call 4 task scope sub-commands which gives you the desired level of abstraction: \begin{LstWs} \item \verb|cipux_task_create_student_account| \item \verb|change_user_account_firstname| \item \verb|change_user_account_lastname| \item \verb|change_student_account_password| \end{LstWs} If you use this sub-commands you could avoid using discrete LDAP attributes. On other thing is that you should choose the smallest object scope as possible. So choosing the next list of commands is a little bit better, because your client would need less access rights to do it. \begin{LstWs} \item \verb|cipux_task_create_student_account| \item \verb|change_student_account_firstname| \item \verb|change_student_account_lastname| \item \verb|change_student_account_password| \end{LstWs} If some sub-commands are missing in in you installation or in \CipUX, this sub-commands can be added to the configuration file \verb|/etc/cipux/cipux-task.perl| or \verb|/usr/share/cipux/etc/cipux-task.perl|\footnote{You can find example sub-commands in this file. You can add something or modify this. However this is the location of the CipUX::Task boostrap configuration and will be overwritten when you install a new version.} locally or better you write just your additional sub-commands in a newly created file with the ending \verb|.perl| under \verb|/usr/share/cipux/etc/cipux-task.d/your_name.perl|. This will be additionally read in by the \CipUX{} configuration space. The best way is of course to share your new sub-commands on the mailing list cipux-devel. This will give you feedback. You might get hints on improvement or your commands will even be be included in the next CipUX-Task release. \subsubsection{More than one call parameter} It is possible to add other attributes to a call. However this version of CipUX do not track the possibility if such an attribute can be used or not. The LDAP server in the last instance will decide what is possible and what not. To get an idea what is possible it is a good idea to look at all cipux-task.perl configuration files. But also additional attributes are possible. It is planned that an introspective command of the RPC will be written to support a query, for each object, to see what is possible. For now the LDAP schemata are the reference. Using the \verb|cipux_task_client| is also a quick method to find out what is possible when you add the \verb|--debug| switch on the command line. \begin{shell} cipux_task_client -t cipux_task_create_teacher_account -o boromir \ -x cipuxFirstnanme=Boromir -x cipuxLastname='Son of Denethor II.'\ --debug \end{shell} This is equivalent to the following \lstinline|$param_hr| perl code. \begin{perlcode} \$param_hr = { object = 'boromir', cipuxFirstname = 'Boromir', cipusLastname = 'Son of Denethor II.', }; \end{perlcode} It is not possible to show the whole output here, but you will get an idea, what additional LDAP attributes are possible for the \lstinline|$param_hr| keys if you read the output. This and the next outputs are slightly modified to fit on the page. \begin{out} ... CipUX::Storage::add_node <461>: -> found obj.Class: objectClass CipUX::Storage::add_node <470>: -> found obj.Class name: posixAccount CipUX::Storage::add_node <486>: --> mandatory attr: uid CipUX::Storage::add_node <486>: --> mandatory attr: gidNumber CipUX::Storage::add_node <486>: --> mandatory attr: homeDirectory CipUX::Storage::add_node <486>: --> mandatory attr: uidNumber CipUX::Storage::add_node <486>: --> mandatory attr: cn CipUX::Storage::add_node <499>: --> auxiliary attr: description CipUX::Storage::add_node <499>: --> auxiliary attr: gecos CipUX::Storage::add_node <499>: --> auxiliary attr: loginShell CipUX::Storage::add_node <499>: --> auxiliary attr: userPassword ... \end{out} This section for example logs the output of the subroutine \verb|add_node|. It tells in details which attributes are mandatory or optional. Keep in mind that \verb|cipux_task_client| will throw an exception when a mandatory option is missing. This options are mandatory in terms of LDAP and sometimes not mandatory for the client to provide. So you can imagine that \CipUX{} tries hard to automatically calculate many mandatory and auxiliary values. Some of them can be overwritten on the fly. A better approach might be to overwrite them in a second call or they should be changed globally in a configuration file if you intend to always overwrite them. This is because a automatically calculated value might be used as an input to a second automatically calculated value. So you have to try or to understand what you do and what effect do this have. If unsure changing later is the better approach. \begin{out} 2009-12-17+23:36:24 CipUX::Storage::add_node <407>: BEGIN 2009-12-17+23:36:24 CipUX::Storage::add_node <408>: > obj: boromir 2009-12-17+23:36:24 CipUX::Storage::add_node <409>: > type: cipux_account.user 2009-12-17+23:36:24 CipUX::Storage::add_node <410>: > attr_hr: $VAR1 = { 'cipuxHardQuota' => 200000, 'cipuxIsAccount' => 'TRUE', 'cipuxInternetStatus' => 'accept', 'cn' => [ 'Boromir Son of Denethor II.' ], 'cipuxLastname' => [ 'Son of Denethor II.' ], 'uidNumber' => [ 10157 ], 'cipuxRemark' => 'CipUX task layer', 'cipuxSkeletonUid' => [ 'none' ], 'cipuxMail' => [ 'boromir@tjener' ], 'cipuxStatus' => 'idle', 'cipuxRole' => 'teachers', 'gecos' => [ 'boromir' ], 'userPassword' => [ '{crypt}GyDkuuhqJi/rU' ], 'cipuxSoftQuota' => 100000, 'uid' => [ 'boromir' ], 'homeDirectory' => ['/skole/tjener/home0/boromir'], 'objectClass' => [ 'posixAccount', 'top', 'shadowAccount', 'imapUser', 'cipuxAccount' ], 'gidNumber' => [ 10157 ], 'cipuxFirstname' => 'Boromir', 'cipuxCreationDate' => [ '2009-12-17T23:36:23' ], 'mailMessageStore' => ['/var/lib/maildirs/boromir'], 'loginShell' => '/bin/bash' }; \end{out} If you read further in the debug output you can find whole hash trees for LDAP object like this one. But be aware of just copying those structures to your XML-RPC client, because this is only on small part of what happens if a user account is created. \subsubsection{Exceptions} If you create a user account (eg. ``frbeutlin'') and try to create this twice which is of course not possible you will most likely get an exception similar like this one. \begin{out} (EXCEPTION) Can not add entry \ [cn=frbeutlin,ou=Group,dc=skole,dc=skolelinux,dc=no]! \ (Already exists) \end{out} This exception are triggered from the object layer and are given back to the client over the XML-RPC server. You have to catch those exceptions. \subsubsection{Access to a task} Every task hast its own access check when requested via RPC. If we for example code this \lstinline|'ttl'| call: \begin{perlcode}[commandchars=\\\[\],label={Call ttl as a task}] my $pay_hr = { header_hr => $header_hr, login => 'cipadmin', ticket => $some_valid_ticket, cmd => \fbox['ttl'], param_hr => {}, }; my $answer_hr = $server->call( \fbox['task'], $pay_hr); \end{perlcode} Then we will be astonished because \verb|cipadmin| has \emph{no right} to execute the task \lstinline|'ttl'| and the answer will be harsh. \begin{out} \$answer_hr = { 'msg' => 'No access for [cipadmin] to [ttl]', 'ltarget' => 'NULL', 'cmd' => 'ttl', 'cmdres_r' => {}, 'status' => 'FALSE', 'header_hr' => $sever_header_hr, 'type' => 'href', 'ticket' => 'c73ce0812fd68a2345254634dab64e24', 'login' => 'cipadmin' }; \end{out} We learn earlier that the \lstinline|'ttl'| command can be accessed by everyone. Why can the access then be denied? Well, a common mistake is to think that \lstinline|'ttl'| is a \CipUX{} task (to be correctly a task scope sub command). It is not! The ``\verb|ttl|'' is a RPC scope by itself. The call which generates this ``access denied'' called the ``\verb|task|'' scope with the cmd \lstinline|'ttl'|. Instead we should call the \lstinline|'ttl'| command with the right scope \verb|ttl|. \label{call_ttl_as_ttl} \begin{perlcode}[commandchars=\\\[\],label={Call ttl as a ttl}] my $pay_hr = { header_hr => $header_hr, login => 'cipadmin', ticket => $some_valid_ticket, cmd => \fbox['ttl'], param_hr => {}, }; my $answer_hr = $server->call( \fbox['ttl'], $pay_hr); \end{perlcode} This will give us the correct answer from the server. \begin{out} \$answer_hr = { 'msg' => '', 'problem' => '0', 'ltarget' => 'NULL', 'cmd' => 'ttl', 'cmdres_r' => { 'ttl' => '600' }, 'status' => 'TRUE', 'header_hr' => $HEADER_HR, 'type' => 'href', 'ticket' => '1223457de8afce5c0bcae9092833d082', 'login' => 'cipadmin' }; \end{out} This was an example of calling an unknown sub-command like \lstinline|'ttl'| in the RPC scope \verb|task| and getting an ``access denied'' as an answer. If you are calling a real \verb|task| sub-command (for example \lstinline|'cipux_task_list_student_accounts'|) you have the chance that the user has access to this. Because \emph{cipadmin} is in the \emph{admin} group he should have access to all \verb|task| sub-commands. For other users this depends on the role based access control (RBAC) module which can not be explained here in detail. Basically you can assume that if the RPC client is registered and has registered its \verb|task| sub-commands and a group is assigned to that client and a user is assigned to that group the user has access to that tasks. \section{Toolbox} This section describes some programs coming with the XML-RPC server which might be useful for testing and debugging. \subsection{cipux\_rpcd} The most important tool is the server by itself. Normally started you might get some information as log messages through you system log facility. However this is limited. To get more information the sever has a debug mode. Of course you have to stop the running server for this. \begin{shell} /usr/sbin/cipux_rpcd --debug \end{shell} This will print a lot of messages on the console! Of course this is not a good way to operate that server in production mode - it slows everything down, but it helps developing a XML-RPC client or to see if something is wrong. \CipUX{} uses an advanced logging facility Log::Log4perl. This was developed for Java and adopted for Perl. More information can be found at CPAN. You can find the configuration in log4perl.conf. Be aware that \CAT{} uses its own file. Sometimes this will generate too much messages. You can use the Log::Log4perl configuration to switch on and off messages, filter them, redirect them. In principle you can restrict logging to certain \CipUX{} Perl modules or even subroutines. After the logging configuration has changed the server has to be restarted before it takes effect. \subsection{cipux\_rpc\_list} This script uses the same configuration space as the XML-RPC server. Its only duty is to list all calls and all sub-commands in a long list. Even though this a good thing to have in this output calls and sub-commands are not distinguished. Therefore this is a very simple method for checking what is available in terms of shell scripts. If the server gets an introspection mechanism in the future this command should be enhanced/replaced accordingly. \begin{shell} cipux_rpc_list|grep -v cipux_task \end{shell} Since all sub-commands in the RPC scope \verb|task| starting with \verb|cipux_task| the trick with the \verb|grep -v| do the trick to list all scopes. \begin{out} ping version sum login logout session ttl task \end{out} \subsection{cipux\_rcp\_test\_client} The test client can be used to test a locally installed \CipUX\ XML-RPC server. The usage is straight forward and self explanatory. Prepare yourself with the login of \verb|cipadmin| and the password. The test script will create and delete some test accounts. %\input{cipux_rpc_test_client} The \verb|cipux_rcp_test_client| runs several RPC calls and try to figure out if the answers are valid or not. It can therefore used to test if the XML-RPC behaves correctly in some ways. Because the \verb|cipux_rcp_test_client| execute real tasks like creating and deleting users and groups on the system there is the possibility to delete accounts that should not be deleted. Even if this is not very likely it is more likely to corrupt the LDAP database in terms of data validity and because of this risk it is advised to uses this command after a LDAP backup and not on productive systems unless you can assure that the LDAP data before running and after running is the same in terms of validity. So keep in mind until someone volunteers to enhance it, this program is not bullet-proof. \begin{shell} cipux_rcp_test_client \end{shell} This will outputs hundreds of lines. The last lines might look like this ones: \begin{out} ~ Test 0305: CipUX::Task 03 create netgroup exec SUCSESS Test 0306: CipUX::Task 04 create netgroup list SUCSESS Test 0307: CipUX::Task 05 create netgroup result SUCSESS Test 0308: CipUX::Task 06 add client to netgroup exec SUCSESS Test 0309: CipUX::Task 07 add client to netgroup list SUCSESS Test 0310: CipUX::Task 08 add member to netgroup result SUCSESS Test 0311: CipUX::Task 09 erase client 's of netgroup list SUCSESS Test 0312: CipUX::Task 10 destroy netgroup exec SUCSESS Test 0313: CipUX::Task 11 destroy netgroup list SUCSESS Test 0314: CipUX::Task 12 destroy netgroup result SUCSESS ---------------------------------------------------------------------- SUCSESS: 314 FAILURE: 0 ---------------------------------------------------------------------- \end{out} An other test of the script is also very useful: testing if the LDAP database is more or less the same before and after this script was executed. To perform this test you have to be root or at least you have to have the execution rights for slapcat. \begin{shell} slapcat > before ~$ cipux_rpc_test_client ~$ slapcat > after ~$ diff --ignore-matching-lines '^modifyTimestamp' \ --ignore-matching-lines '^entryCSN' before after \end{shell} If you have no output of the \verb|diff| command the LDAP is more or less the same as before. \subsection{cipux\_rpc\_test\_repetition} The \verb|cipux_rpc_test_repetition| script can be called simply \begin{shell} cipux_rpc_test_repetition --time 2000 \end{shell} It will run for the specified time in seconds. To test the default ticket life span of 1200 seconds a value larger then 1200 for example 1800 (30 minutes) should be used. For 1 minute we get this output at the end of the job. \begin{out} ======================================================================== pay ticket 13e777a207fb292f3a1e3d94d8fcedc9 default channel ticket 13e777a207fb292f3a1e3d94d8fcedc9 cipux_task_list_user_accounts at 1260214026 status TRUE \ (13e777a207fb292f3a1e3d94d8fcedc9) pay ticket 13e777a207fb292f3a1e3d94d8fcedc9 default channel ticket adf89d46c9e167b5758adbaf405110ac explicit channel ticket adf89d46c9e167b5758adbaf405110ac ======================================================================== pay ticket adf89d46c9e167b5758adbaf405110ac default channel ticket adf89d46c9e167b5758adbaf405110ac cipux_task_list_user_accounts at 1260214026 status TRUE \ (adf89d46c9e167b5758adbaf405110ac) pay ticket adf89d46c9e167b5758adbaf405110ac default channel ticket d9f3a0acce41b72a5c4dbff23f2e8c9c explicit channel ticket d9f3a0acce41b72a5c4dbff23f2e8c9c Summary: start at: 1260213967 stop at: 1260214027 seconds: 60 true : 291 false : 0 \end{out} \section{CAT - other \CipUX{} XML-RPC clients} A CipUX Administration Tool (\CAT) do not have to be a XML-RPC client. At the time of this writing all of them are. One reason is that a XML-RPC clients for \CipUX{} to not have to have root rights to access the XML-RPC server. Some of the existing clients are Web clients, where it is out of question running this clients with root rights. Another reason is that \CipUX{} XML-RPC hide most of the complexity of the storage layer from the client and that the objects which are created are the same among all clients. Because \CipUX{} functions as an abstraction layer between the clients and the operation system additional features can be implemented and used among all clients. This section tries to summarize all \CAT s to give an overview about the current development as well as a starting point regarding the search for further informations about different clients. The order of this sections represented the age of the tool. \subsection{CAT-Web} The development of CAT-Web started as CAT in the year 2000 when there was a need for a \CipUX{} Administration Tool. The first university internal release 1.0 was a standalone CGI program for the apache web server. For the version 2.0.0 CAT was converted to a standard webmin module and used up to version 3.2.16 with root rights. This approach was obsolete when webmin was expelled from Debian. The name changed from CAT ot CAT-Web and is basically a rewrite from scratch. Some pre-releases that already uses the XML-RPC server where made public in 2007 - 2009. The next version 3.4.0.0 will be released ending 2009 or beginning 2010 and is a CGI application for apache (and probably other web servers) written in object orientated Perl that supports template driven skinning. Home page: \href{http://www.cipux.org}{http://www.cipux.org} (English) \subsection{CipuxPHP} %my text %The starting point of this software where actually %before the CipUX XML-RPC server by itself where %developed. The first version was developed by Benoit Sonntag %in a real time programming session with the French %Skolelinux Team in Forbach, France in July 2005. %Jean-Charles Siegel took over the development in %2006 at the great summer meeting at ZZZ-Mines and %developed the second version which was able to access %CipUX 3.2.6, which was deployed at the French Add-On %CD for Skolelinux. The third major version was %developed by Jean-Charles Siegel and Christophe %Gossen for an university project that supports a %non official release of CipUX. The forth major version %development started again Jean-Charles Siegel %together with the French Skolelinux Team in 2009 for %CipUX 3.4.0.x. This is still under development. %JCS improvements % private mail 2009-12-11 (license GPLv2 or later 2009-12-14) CipuxPHP, is a web interface for managing users on Skolelinux. It's a module for the CMS Moodle, but it was also written to work in a stand-alone mode in the case of Moodle isn't installed. The starting point of this software where actually before the \CipUX{} XML-RPC server by itself where developed. \textbf{2005:} The first version was developed by Benjamin Sonntag in a real programming session with the French Skolelinux Team in Forbach, France in July 2005. \textbf{2006:} Jean-Charles Siegel took over the development in Summer 2006 at the great summer meeting at Carreau Wendel's – Mines Museum (Petite-Rosselle, France) and developed the second version that was able to access \CipUX{} 3.2.6, which was deployed at the French Add-On CD for Skolelinux. A second big dev-camp in Extramadura's Summer University (Spain's area) was the theater of an important development of CipUX. \textbf{2007:} The third major version was developed by Jean-Charles Siegel and Christophe Gossen for an university project that supports a non official release of \CipUX{} in 2007. It was written in PHP object, using templates to display the web pages. \textbf{2010:} The forth major version development started again Jean-Charles Siegel together with the French Skolelinux Team in 2010 for \CipUX{} 3.4.0.x. This is still under development. The goal of cipuxPHP is to \emph{manage easily} the pupils in Skolelinux. Create, modify, delete them. But also manage classes (considered as groups) and move pupils from one class to an other. Home page: \href{http://wiki.skolelinux.fr/CipuxXmlRpc}{http://wiki.skolelinux.fr/CipuxXmlRpc} (French) \begin{flushright} \footnotesize(Jean-Charles Siegel) \end{flushright} \subsection{CipUX-Passwd} CipUX::Passwd is a Perl module and a command line script that can be used as an CipUX XML-RPC client to set once own password. It was developed by Christian K\"ulker in 2009 and released as CipUX-Passwd-3.4.0.2. \subsection{CATweasel} %my text %This project was started by Jochen Breuer in 2009 with the aim to develop a %full featured CipUX Administration Tool with a good usability. % JB text, mail on cipux-devel 2009-12-11 (license GPLv2 or later 2009-12-14) The project was started in early 2009 for the RLP-Skolelinux project. Main contributor is the ed-media GmbH from Kaiserslautern Germany. Debian maintainer for CATweasel is Jonas Smedegard. Many thanks for that Jonas! CATweasel was started because there was a steady demand for more interactivity in the web UI for CipUX. Klaus Knopper then decided to contract the ed-media GmbH to start a new project that should establish a basis on a modern web framework to accomplish that. That was the birth of CATweasel. At the moment there is no fancy website with lots of screenshots. Just a boring Wiki with docu and the repository on bitbucket:\\ \href{http://bitbucket.org/edmedia/catweasel/wiki/Home}{http://bitbucket.org/edmedia/catweasel/wiki/Home} \begin{flushright} \footnotesize(Jochen Breuer) \end{flushright} CipUX-RPC-3.4.0.9/doc/tecdoc/cipux_xml_rpc_server_and_client_fr.tex000444001750001750 612511432067074 26102 0ustar00ckuelkerckuelker000000000000% +=========================================================================+ % || Begin || % +=========================================================================+ \documentclass[a4paper,twoside,12pt,cleardoubleempty,footsepline,headsepline,idxtotoc]{scrartcl} % +=========================================================================+ % || Preamble || % +=========================================================================+ \include{tecdoc_preamble} \usepackage[frenchb]{babel} \usepackage{ucs} \usepackage[utf8x]{inputenc} % +=========================================================================+ % || Begin Document || % +=========================================================================+ \begin{document} % +=========================================================================+ % || Title || % +=========================================================================+ \title{\smaller Le \CipUX{} XML-RPC serveur et client\\ \small Communication au-delà des frontières } \date{Version 3.4.0.9} \author{Christian K\"ulker\\ \small Traduction Gundula Redecke} %\begin{fullpage} \maketitle %\end{fullpage} % +=========================================================================+ % || Abstract || % +=========================================================================+ \begin{abstract} Ce document traite la specification de la communication {\CipUX} XML-RPC entre le serveur et son client ainsi que son utilisation. L'API (l'interface des applications programmées) du serveur est en expliqué en detail avec des exemples pour l'accès au serveur et donnés en Perl. D'autres développeurs d'un client XML-RPC devraient être capable d'acquérir les informations importantes via les exemples donnés pour développer sans encombre leur propres applications. Pour compléter ce document, il existe des sections qui traîtent des outils concernant le réajustement des fautes des programmes ainsi que des informations sur les projets concernants le client libre XML-RPC déjà existant. \end{abstract} % +=========================================================================+ % || Table of Content || % +=========================================================================+ %\begin{fullpage} \tableofcontents %\end{fullpage} \newpage % +=========================================================================+ % || Content || % +=========================================================================+ \include{rpc_fr} % +=========================================================================+ % || End Document || % +=========================================================================+ \end{document} CipUX-RPC-3.4.0.9/doc/tecdoc/rpc_fr.tex000444001750001750 21160011432067074 20300 0ustar00ckuelkerckuelker000000000000% +=====================================================================+ % || Content || % +=====================================================================+ \section{Préface} Ce document est sensé pour supporter les développeurs des clients XML-RPC et décrit notamment des parties pratiques des appels et réponses du serveur XML-RPC en détail. Le document fait l'impasse sur d'autres parties comme l'installation. Au début d'une section on trouve des parties différentes du paquet de logiciel et la configuration du serveur principal. Également il est inclus une partie concernant l'usage du serveur dans une manière assurée. La section suivante introduit brèvement le concept de la session, et puis un tableau pour représenter les categories des appels d'un serveur XML-RPC est dessiné. Pour les gens impatients, deux appels d'exemple et leurs réponses sont affichés dans une propre section. L'un des exemples consiste d'un appel "sum" didactique, quand l'autre est un appel réel assez simple du domaine des tâches. Les deux sections suivantes décrirent la construction de l'appel de client et la réponse du serveur plus spécifique. La section la plus importante du document précise tous les domaines du serveur et leur sous-commandements en détail. L'accent de ce texte est mis sur la domaine des tâches. Au fond du document on trouve la section des outils, laquelle introduit des programmes et renseignements pour tester et trouver les fautes des clients XML-RPC. La dernière section est un sommaire des outils pour l'administration déjà développés. Ces projets du logiciel source ouvert libre sont tenu prêt comme un point de départ pour le rassemblement des informations pratiques en plus. Merci à Jean-Charles Siegel et Jochen Breuer pour une contribution dans cette section. \section{Parties du \CipUX{} serveur XML-RPC} Le paquet de logiciel CipUX-RPC-3.4.0.9.tar.gz est un paquet de standard CPAN. La dernière version du paquet peut être téléchargé vers l'aval de l'adresse suivante \href{http://release.cipux.org}{http://release.cipux.org}. Cette vue global ne peut pas expliquer comment un paquet de CPAN fonctionne, mais qu'est-ce qu'un tel paquet contient et qu'est-ce que cela installe dans le système de fichiers. Le paquet contiens à la base deux approches (A) + (B), qui peuvent (dans le futur) se fondre dans une approche. (A) le \CipUX{} serveur XML-RPC et (B) la couverture de sécurité pour "stunnel4". En plus il contient (C) quelques outils. Il faut au moins (A) pour avoir un \CipUX{} serveur XML-RPC simple. Si on installe en plus (B) on peut utiliser une connection au service de transfert fiable par la couverture "stunnel4". Et pour finir on peut aussi installer et utiliser (C). On peut tester quelques ascpects du serveur XML-RPC et utiliser la liste cipux\_rpc\_list comme une méthode appropriée pour établir une liste de tous les appels du XML-RPC et avec tous les sous-commandements de la domaine des tâches. Des scripts à (D) sont approvisionné dans le code source qui est publié "upstream tar" pour l'utlisation pendant des études. Pour certains de ces scripts il y a de la référence, quelques sont expliqués ou bien inclus dans ce document. \begin{verbatim} (A) /usr/share/perl5/CipUX/RPC.pm /usr/share/perl5/CipUX/RPC/Server.pm /usr/share/perl5/CipUX/RPC/Server/Daemon.pm /usr/sbin/cipux_rpcd /etc/init.d/cipux-rpcd /usr/share/cipux/etc/cipux-rpc.ini \end{verbatim} \begin{verbatim} (B) /etc/cipux/stunnel/readme.txt /etc/cipux/stunnel-cert.conf /etc/cipux/stunnel.conf /etc/init.d/cipux-rpcdr /usr/sbin/cipux_mkcertkey /usr/sbin/cipux_rpcdr \end{verbatim} \begin{verbatim} (C) /usr/bin/cipux_rpc_list /usr/share/perl5/CipUX/RPC/Test/Client.pm /usr/sbin/cipux_rpc_test_client /usr/bin/cipux_rpc_test_repetition \end{verbatim} \begin{verbatim} (D) doc/example/bin/expl_rpc_ping doc/example/bin/expl_rpc_session doc/example/bin/expl_rpc_task_create_destroy doc/example/bin/expl_rpc_task_list doc/example/bin/expl_rpc_task_member doc/example/bin/expl_rpc_task_sum \end{verbatim} \section{La configuration du serveur} Le \CipUX{} serveur XML-RPC est un script de Perl qui s'appelle \verb|cipux_rpcd| avec ses modules attachés \verb|CipUX::RPC*|. Le script du serveur peut être trouvé (cela dépend de votre installation) par exemple sous \verb|/usr/sbin/cipux_rpcd|. Le préreglage de la configuration du serveur est, par exemple, à \verb|/usr/share/cipux/etc/cipux-rpc.ini|. Cette configuration peut être écrasée par autres fichiers. Ce document n'expliquera pas l'espace de configuration ici. C'était justement pour donner quelques idées et pour montrer, ce qu'il ne suffit pas de décomposer un seul fichier. La configuration à écraser par d'autres fichiers peut être trouvé à: \verb|/etc/cipux/cipux-rpc.ini| ou bien \verb|~/.cipux/cipux-rpc.conf|. Les directives de la configuration valides de ce fichier - laquelles sont essentielles à savoir pour être capable de programmer les clients de XML-RPC - sont: \begin{filecontent} xml_rpc_port = 8001 xml_rpc_address = localhost xml_rpc_proto = tcp intern_admin_group = admin \end{filecontent} Certains des valeurs préréglés sont déjà changeable, autres valeurs sont envisagé de dévenir changeable dans le futur. Ce set de valeurs décrit le simple \CipUX{} serveur XML-RPC au port 8001. Il est bon d'utiliser ce port dans le mode "socket" sur l'hôte local. De toute façon il ne faut pas utiliser ce port pour une communication éloigné par un réseau. Maintenant, pour le moment, le chiffrement "SSL" est executé par la couverture "stunnel4", mais cela peut être integré dans le serveur de préréglage. Si vous planifiez de communiquer par réseau, il est bon d'utiliser le port déjà défini par la configuration "stunnel4" pour CipUX. Par préréglage, normalement le port est 8000. %\subsection{Secure connection to the XML-RPC Server} Le serveur \CipUX\ XML-RPC dans la version jusqu'à 3.4.0.7 traîte des demandes de \emph{http} mais ne traîte pas les demandes de \emph{https}. Pour sécuriser la connexion on doit utiliser un logiciel différent. Une possibilité de réaliser cela consiste en stunnel4 lequel est la solution du standard pour les connexions sécurisées au serveur \CipUX\ XML-RPC jusqu'à maintenant. Il n'y a pas grande chose à dire sur ce logiciel. Le script en "Perl" \verb|cipux_rpcdr| est une couverture de stunnel4 avec un script de début \verb|/etc/init.d/cipux-rpcdr| et un fichier de configuration séparé \verb|stunnel.conf| et des certificats. Par préréglage il accepte les demandes https sur le port 8000. Pour recevoir des informations supplémentaires, veuillez consulter la documentation du stunnel4. \newpage \section{La communication via session} La communication avec le serveur XML-RPC est dirigé vers l'avant. L'image suivante décrit une session simplifiée. %\begin{verbatim} %0 client ---> ping ---> server %1 client <--- answer <--- server % %2 client ---> login ---> server %3 client <--- answer(session) <--- server % %4 client ---> list(session) ---> server %5 client <--- answer <--- server % %6 client ---> logout(session) ---> server %7 client <--- answer <--- server %\end{verbatim} \begin{center} \begin{tikzpicture} [server/.style={circle,draw=blue!50,fill=blue!20,thick}, client/.style={rectangle,draw=black!50,fill=black!20,thick}, c2s/.style={->,shorten <=1pt,>=latex,thick}, s2c/.style={->,shorten >=1pt,>=latex,thick}, c2sw/.style={auto,rotate=-5}, s2cw/.style={auto,rotate=5,swap}, ] %\node (c0) at ( 0,0) [client] {client}; %\node (s1) at (12,1) [server] {server} edge [s2c] node [s2cw] {answer} (c0); \node (c2) at ( 0,2) [client] {client}; \node (s3) at (12,3) [server] {serveur} edge [s2c] node [s2cw] {réponse} (c2);% 8 \node (c4) at ( 0,4) [client] {client} edge [c2s] node [c2sw] {quitter(session)} (s3);% 7 \node (s5) at (12,5) [server] {serveur} edge [s2c] node [s2cw] {réponse} (c4);% 6 \node (c6) at ( 0,6) [client] {client} edge [c2s] node [c2sw] {list(session)} (s5);% 5 \node (s7) at (12,7) [server] {serveur} edge [s2c] node [s2cw] {réponse(session)} (c6);% 4 \node (c8) at ( 0,8) [client] {client} edge [c2s] node [c2sw] {inscription} (s7);% 3 \node (s9) at (12,9) [server] {serveur} edge [s2c] node [s2cw] {réponse} (c8);% 2 \node (c10) at ( 0,10) [client] {client} edge [c2s] node [c2sw] {ping} (s9);% 1 \end{tikzpicture} \end{center} \newpage Si vous procédez des sessions plus longues avec vos clients - lequel n'est pas recommandable pour les applications web à cause de leur nature fragile (beaucoup de pièges de temps de débouclement) - il faut s'occuper soigneusement de la clef du session. Voilà un exemple d'une communication qui renouvelle la clef du session. L'appel "ttl" n'est pas obligatoire, on peut l'utiliser pour tester combien de temps une session peut survivre. %\begin{verbatim} % 0 client ---> ping ---> server % 1 client <--- answer <--- server % % 2 client ---> login ---> server % 3 client <--- answer(session1) <--- server % % 4 client ---> ttl(session1) ---> server % 5 client <--- answer <--- server % % 6 client ---> someting(session1) ---> server % 7 client <--- answer <--- server % % 8 client ---> session(session1) ---> server % 9 client <--- answer(session2) <--- server % %10 client ---> someting(session2) ---> server %11 client <--- answer <--- server %12 client ---> logout(session2) ---> server %13 client <--- answer <--- server %\end{verbatim} \begin{center} \begin{tikzpicture} [server/.style={circle,draw=blue!50,fill=blue!20,thick}, client/.style={rectangle,draw=black!50,fill=black!20,thick}, c2s/.style={->,shorten <=1pt,>=latex,thick}, s2c/.style={->,shorten >=1pt,>=latex,thick}, c2sw/.style={auto,rotate=-5}, s2cw/.style={auto,rotate=5,swap}, ] \node (c0) at ( 0,0) [client] {client}; \node (s1) at (12,1) [server] {serveur} edge [s2c] node [s2cw] {réponse} (c0);% 14 \node (c2) at ( 0,2) [client] {client} edge [c2s] node [c2sw] {quitter(session2)} (s1);% 13 \node (s3) at (12,3) [server] {serveur} edge [s2c] node [s2cw] {réponse} (c2);% 12 \node (c4) at ( 0,4) [client] {client} edge [c2s] node [c2sw] {quelque chose(session2)} (s3);% 11 \node (s5) at (12,5) [server] {serveur} edge [s2c] node [s2cw] {réponse(session2)} (c4);% 10 \node (c6) at ( 0,6) [client] {client} edge [c2s] node [c2sw] {session(session1)} (s5);% 09 \node (s7) at (12,7) [server] {serveur} edge [s2c] node [s2cw] {réponse} (c6);% 08 \node (c8) at ( 0,8) [client] {client} edge [c2s] node [c2sw] {quelque chose(session1)} (s7);% 07 \node (s9) at (12,9) [server] {serveur} edge [s2c] node [s2cw] {réponse} (c8);% 06 \node (c10) at ( 0,10) [client] {client} edge [c2s] node [c2sw] {ttl(session1)} (s9);% 05 \node (s11) at (12,11) [server] {serveur} edge [s2c] node [s2cw] {réponse(session1)} (c10);% 04 \node (c12) at ( 0,12) [client] {client} edge [c2s] node [c2sw] {inscription} (s11);% 03 \node (s13) at (12,13) [server] {serveur} edge [s2c] node [s2cw] {réponse} (c12);% 02 \node (c14) at ( 0,14) [client] {client} edge [c2s] node [c2sw] {ping} (s13);% 01 \end{tikzpicture} \end{center} \section{Les catégories des appels RPC} Les appels montrés en haut - comme \verb|list| - sont, bien-sûr, simplifiés. Plus tard, l'appel list sera introduit en détail. Pour savoir quelle sorte d'appel on a besoin, on peut les catégoriser comme suit: \begin{LstBs} \item des appels simples (principalement pour tester quelque chose): \verb|sum| \item des appels d'accessibilité: \verb|ping| \item des appels d'autorisation: \verb|login, logout| \item des appels de gestion de la session: \verb|session, ttl| \item des appels de tâche avec de sous-commandes: appel: \verb|task| (sous-commandes: \verb|cipux_task_*|) %\item rpc server management calls: \verb|rpc_info, rpc_intern| \end{LstBs} Si vous écririez un client d'un \CipUX{} RPC, principalement vous avez besoin seulement les appels du \LstBsN{2}, \LstBsN{3}, \LstBsN{4} et \LstBsN{5}. A la phase de développement, vous pouvez utiliser occasionnellement le \LstBsN{1}. Notamment si vous êtes débutant à développer le RPC, le \LstBsN{1} peut être un bon point de départ. \section{Example call and answer} Before more details are presented and explained this section will just provide two examples \verb|sum| and \verb|task| of a client call and the server response with discrete values for the impatient. The examples are taken from the \verb|cipux_rpc_test_client| script. \subsection{Sum call example} \label{full_sum_call_example} Here is the client code for \lstinline|'sum'| call with its \verb|sum| command. A simple call and command that do not need a ticket but it need two parameters: %\begin{lstlisting}[caption=Descrete sum call example,emph={cipux_task_sum},emphstyle={\color{red}\underbar}] %\begin{Verbatim}[commandchars=\\\[\]] \begin{perlcode}[commandchars=\\\[\],label={Discrete sum call example}] my $https_url = "https://localhost:8001/RPC2"; my $server = Frontier::Client->new( url => $https_url ); my $pay_hr = { # hash ref of payload header_hr => { # header is part 1 of payload cipux_version => '3.4.0.0', client_name => 'cipux_rpc_test_client', client_version => '3.4.0.0', rpc_version => '2.0', client_key => 'dummy', client_cred => 'dummy', gmt_time => '1195349030', }, # some important key-value pairs login => 'dummy', ticket => 'dummy', \fbox[cmd => 'sum'], param_hr => { # second part of payload: parameter 'summand2' => '4', 'summand1' => '3', }, }; my $answer_hr = $server->\fbox[call( 'sum'], $pay_hr ); \end{perlcode} %\end{lstlisting} And here is the value of \lstinline|$answer_hr| which comes from the server displayed in the Data::Dumper format: \begin{out} \$answer_hr = { 'header_hr' => { 'cipux_version' => '3.4.0.0', 'server_key' => '', 'server_cred' => '', 'gmt_time' => '1195349220', 'server_version' => '3.4.0.0', 'server_name' => 'cipux_rpcd', 'rpc_version' => '2.0' }, 'cmd' => 'sum', 'ticket' => 'dummy', 'login' => 'dummy', 'status' => 'TRUE', 'type' => 'aref' 'cmdres_r' => [ '7' ], }; \end{out} \subsection{Task call example} \label{full_task_call_example} The task call sub-command \verb|cipux_task_list_student_accounts| is a real world CipUX::Task XML-RPC example. It is is also a good example for a simple parameterless call. One part, which is also different from the last call is, that a login and ticket is required. \begin{perlcode}[commandchars=\\\[\],label=Discrete list call example] my $https_url = "https://localhost:8001/RPC2"; my $server = Frontier::Client->new( url => $https_url ); my $pay_hr = { header_hr => { cipux_version => '3.4.0.0', client_name => 'cipux_rpc_test_client', client_version => '3.4.0.0', rpc_version => '2.0', client_key => 'dummy', client_cred => 'dummy', gmt_time => '1195349030', }, login => 'cipadmin', ticket => '48df73accd8530af69f97cf1c847f29e', cmd =>\fbox['cipux_task_list_student_accounts'], param_hr => { }, }; my $answer_hr = $server->call( \fbox['task'], $pay_hr); \end{perlcode} Of course this was a static \lstinline|$pay_hr| call. And the ticket is not valid any more. Therefore this example shows \emph{what} data is transfered or programed not \emph{how} it should be transfered. Hard coded tickets are not at all a solution. And here is the value of \$answer\_hr again in Data::Dumper format: \begin{out} \$answer_hr = { 'msg' => '', 'problem' => '0', 'ltarget' => 'memberUid', 'cmd' => 'cipux_task_list_student_accounts', 'cmdres_r' => { 'students' => { 'cn' => [ 'students' ], 'memberUid' => [ 'bilbo', 'frodo', 'mytest', ] } }, 'status' => 'TRUE', 'header_hr' => { 'server_cred' => '', 'server_key' => '', 'cipux_version' => '3.4.0.0', 'gmt_time' => '1260715448', 'server_version' => '3.4.0.0', 'rpc_version' => '2.0', 'server_name' => 'cipux_rpcd' }, 'type' => 'HASH', 'ticket' => 'dc43ee1170d9514ad7f762f561b4382b', 'login' => 'cipadmin' }; \end{out} \section{Construction of the client call} In general a XML-RPC call can be made by a simple evocation: \bigskip \lstinline{my $answer_hr = $server->call('RPC scope',$pay_hr);} \bigskip This line is made from different parts. \Rmnum{1}. The \lstinline{$answer_hr} which contains the server response explained in section \ref{server_response} on page \pageref{server_response}. \Rmnum{2}. The \lstinline{$server} is a Perl object. See the example client in section \ref{expl_rpc_ping} on page \pageref{expl_rpc_ping} how to get that. \Rmnum{3}. The Frontier::Client key word \lstinline{call}, which is a subroutine in Frontier::Client. You probably do not need to look that up. \Rmnum{4}. The last part represents the parameters to \lstinline{call}. Every call contains two parameters first the \lstinline{'RPC scope'} name (ping, login, task, ...) and second a hash reference \lstinline{$pay_hr} to the payload. \subsection{RPC scope} The RPC scope is the name of the subroutine (\lstinline{sub}) of CipUX::RPC Perl module. The following table shows different RPC scopes and their constrains. \bigskip \rowcolors[\hline]{2}{blue!25}{gray!25} \arrayrulecolor{black!75!gray} \begin{tabular}{|l|l|l|l|}\hline \textbf{RPC scope} & \textbf{require login} &% \textbf{ticket check} & \textbf{ticket renew} \\\hline ping & no & no & no \\ version & no & no & no \\ sum & no & no & no \\ login & yes & yes & no \\ logout & yes & yes & no \\ session & yes & yes & yes \\ ttl & yes & yes & no \\ task & yes (*) & yes (*) & no \\\hline \end{tabular} \bigskip (*) Only for the task \verb|cipux_task_sum| the value is "no". \subsection{Payload} The payload can be assigned through a reference to a hash. This reference are constructed out of 5 mandatory parts (2-6): % ??? \caption{General payload hash} \begin{perlcode}[commandchars=\\\[\],label=General payload hash] \fbox[$pay_hr] = { header_hr => $HEADER_HR, login => $login, ticket => $ticket, cmd => 'sub-command name', param_hr => $param_hr, }; \end{perlcode} \begin{paramlist}{param\_hr} \item[header\_hr] The key \verb|header_hr| demands a reference to a hash as its value: the so called "header". The header as of protocol version 2.0 remains static during the communication, the count of keys are fixed. The values are also more or less fixed. The \verb|gmt_time| value received from the server however can change. The server uses only the \verb|client_name|. Therefore the client should provide and use a registered name. A not registered client might be rejected. Since there is no prove of validity of a client name, the server do not trust a client just because it is registered. Therefore rejecting or not rejecting a registered client is not a matter of security it is just a matter of convenience for the client developer. This behavior might change in the future, but it is foreseen that this has to go along with using other (not jet used) header fields, like \verb|client_key| and \verb|client_cred|. \label{client_header} Since the header remains basically the same, this document will not print the header over and over again. It will use the following header taken from \verb|cipux_rpc_test_client| whenever the hash reference \lstinline|$HEADER_HR| occur. \begin{perlcode}[commandchars=\\\[\],label=General header hash] \fbox[$HEADER_HR] = { cipux_version => '3.4.0.0', client_name => '/usr/bin/cipux_rpc_test_client', client_version => '3.4.0.0', # can be choosen rpc_version => '2.0', client_key => $dummy, # $dummy not used client_cred => $dummy, gmt_time => $epoche, }; \end{perlcode} \item[login] is the user id (uid) of the logged in user. You may not use an empty string. Also keep in mind that this do not refer to numerical user id (uidNumber). \item[ticket] is the valid session ticked of the logged-in user. You may not use an empty string, But some calls need a string. So if the call do not need a ticket ``dummy'' or ``test'' is a good choice. \item[cmd] name of the sub-command. Some RPC scopes do have only one command. Like the RPC scope ``\verb|ping|'' which will have the \lstinline|cmd| ``\verb|ping|''. Other RPC scopes like ``\verb|task|'' to have several hundreds of \lstinline|cmd|s. You may not use an empty string here. \item[param\_hr] is a reference to a hash. It can be a empty reference to a hash but not a reference to an array. The expected content depends on the \verb|cmd| value. And this is related to the call subroutine of CipUX::RPC and secondly to the task subroutine of CipUX::Task. Later parts of this document will provide more hints on how to determine which parameter keys are possible. \end{paramlist} \section{The server response} \label{server_response} This section describes what will be send back from the server and how to interpret this. The client might have implemented this call to the server. \bigskip \lstinline{my $answer_hr = $server->call('RPC scope',$pay_hr);} \bigskip In Perl the answer \lstinline{$answer_hr} of the server is a reference to a hash and is made out of seven parts. \begin{out} \$answer_hr = { header_hr => $server_header_hr, # 1 login => $login, # 2 ticket => $ticket, # 3 cmd => 'sub-command name', # 4 status => 'TRUE|FALSE', # 5 type => 'href|aref|string', # 6 cmdres_r => $cmdres_r, # 7 }; \end{out} \begin{paramlist}{server\_version} \item[header\_hr] is a key in the payload. The value of this key is a hash reference to a ``header'', which (again) remains more or less the same during a session. \begin{minipage}{10cm} \begin{out} \$server_header_hr = { cipux_version => '3.4.0.0', server_name => 'cipux_rpcd', # server name server_version => '3.4.0.0', # not fixed rpc_version => '2.0', server_key => $dummy, # reserved server_cred => $dummy, # reserved gmt_time => time(), # server time }; \end{out} \end{minipage} \medskip See the following subsection for details about the header keys. This answer-header is basically the same as the header which is used in the client call in section \ref{client_header}. \begin{paramlist}{server\_version} \item[cipux\_version] is a key with a scalar value. The \CipUX{} version is given from the server. However only the first 3 digits are significant. The forth digit will probably not change. \item[server\_name] contains a scalar value with the name of the server. Its value is constant and as long nobody else develops a different server it will \verb|cipux_rpcd|. \item[server\_version] has a scalar value of the server version. All four digits of this response are significant. The server will increase this version, if the server was updated and restarted. \item[rpc\_version] represents a scalar value of the \CipUX{} XML-RPC protocol version. This is the version number of the RPC calls. The number is unlikely to be changed within \CipUX{} 3.4.0.y but might change if needed in \CipUX{} 3.4.x.y. Your client should only accept connections up to a discrete version number. \item[server\_key] Not used now. \item[server\_cred] Not used now. \item[gmt\_time] The time is set by the server. \end{paramlist} \item[login] is the uid of the logged in user \item[ticket] is the more or less valid ticked of the logged in user, which was used by the user request. This filed will never provide a new or renewed ticket. you have to use the session call for this. See section \ref{session} on page \pageref{session}. \item[cmd] name of the sub-command \item[status] is key with a boolean scalar value. The status can be \verb|TRUE| or \verb|FALSE|. \item[type] returned data type: href, aref, string \item[cmdres\_r] is key which name derived from command (\verb|cmd|) result (\verb|res|) reference (\verb|_r|). The value is a reference to an array or hash which is constructed like this \lstinline|$cmdres_r = ['a','b','c']| or \lstinline|$cmdres_r = { ttl=> 20 }| % or \lstinline|$cmdres_r = 'OK'| \item[ltarget] is a key which can be used to automatically parse the output of the \verb|cmdres_r| key value. The value of \verb|ltarget| \emph{can} be a LDAP attribute like uidNumber if the target of the task scope sub-command is only one attribute. This feature is considered to be experimental. \end{paramlist} At the moment some calls return other undocumented fields and there might be more fields added in the future. The fields which are documented here are considered to be a part of the version 2.0 of the protocol. You should use them. You could also use the undocumented fields. However they might vanish or change with out warning. If you are missing some fields or if you want that a field will be officially, you can join cipux-devel and make a existing field stable or develop a new field, which can be introduced in the next protocol version 2.2. \section{Calls and responses in detail} The CipUX::RPC call ``ping'' and CipUX::RPC ``sum'' can be used to test the RPC server without authentication. \subsection{ping} \api{ping}{ping}{no}{no}{empty}{hash reference}{empty} The aim of this function is to see if the server is up. The return value will always be the status ``\verb|TRUE|''. No value in \lstinline|$cmdres_r|. So if you are getting a status other then ``\verb|TRUE|'' the server is up but has a problem. If you get no answer the server is not up. Sorry due to the nature of logic it was not possible to implement an answer of ``\verb|DOWN|'' from the server if the server is down. \begin{out} \$status => 'TRUE', $cmdres_r = { } \end{out} This short program tests if the local server is up and running or not. However it does not tell you if the stunnel4 wrapper is working or not. %\begin{perlcode} %\begin{perlcode}[commandchars=\\\[\],label={CipUX RPC test ping client}] \label{expl_rpc_ping} \perlcodefile[label={expl\_rpc\_ping}]{../example/bin/expl_rpc_ping} Example Answer: \begin{out} \$answer_hr = { 'msg' => '', 'ltarget' => 'NULL', 'cmd' => 'ping', 'cmdres_r' => {}, 'status' => 'TRUE', 'header_hr' => { 'server_cred' => '', 'server_key' => '', 'cipux_version' => '3.4.0.0', 'gmt_time' => '1260436207', 'server_version' => '3.4.0.0', 'rpc_version' => '2.0', 'server_name' => 'cipux_rpcd' }, 'type' => 'href', 'ticket' => 'dummy', 'login' => 'dummy' } \end{out} \subsection{version} \api{version}{version}{no}{no}{empty}{hashref}{cipux\_version, server\_version, rpc\_version} The aim of the call is to test the version of the CipUX::RPC server (without logging in) to be able do decide if a login might be possible or not. An other thing is that you can test with this function, if you are able to parse the returned hash reference. The relevant part of the answer looks as follows. \begin{out} 'cmdres_r' => { 'cipux_version' => '3.4.0.0', 'server_version' => '3.4.0.0', 'rpc_version' => '2.0' }, \end{out} \subsection{sum} \api{sum}{sum}{no}{no}{summand1, summand2}{array reference}{a scalar value} The CipUX::RPC server provides two sum function: The first is CipUX::RPC \verb|sum| and the second is and CipUX::Task \verb|cipux_task_sum|. This section is about the simple CipUX::RPC sum function. The aim of this function is to test, if you can send arguments via hash reference and if you can parse the returned array reference. \begin{perlcode} param_hr => { summand1 => 3, summand2 => 4, }, \end{perlcode} The call will return an array reference with a scalar value. See section \ref{full_sum_call_example} on page \pageref{full_sum_call_example} for a full example. \begin{out} 'cmdres_r' => [ '7' ], \end{out} \subsection{cipux\_task\_sum} \api{task}{cipux\_task\_sum}{no}{no}{summand1, summand2}{array reference}{a scalar value} The \verb|cipux_task_sum| is similar to the \verb|sum| call. Except that it is internally invoked in the task section of the XML-RPC server. You have already noticed the difference between the \lstinline|cmd| and \lstinline|call| section above. Therefore rather then testing your client this call can be used to test the rpc server. It is summarized here for the sake of completeness. The call will return an array reference with a scalar value. See section \ref{full_sum_call_example} on page \pageref{full_sum_call_example} for a similar example with the \lstinline|'sum'| call with its \verb|sum| sub command. \subsection{login} \api{login}{login}{yes}{no}{password}{hash reference}{ttl, login, ticket} The login call request 2 parameters. One parameter is given as payload and one parameter will be supplied inside \lstinline|param_hr|. \begin{perlcode}[commandchars=\\\[\],label={login call payload}] \$pay_hr = { param_hr => { \fbox[password => '**********'] }, cmd => 'login', header_hr => { cipux_version => '3.4.0.0', client_key => '', client_cred => '', gmt_time => '1196466596', client_name => 'cipux_rpc_test_client', client_version => '3.4.0.0', rpc_version => '2.0' }, ticket => 'dummy', \fbox[login => 'cipadmin'] }; \end{perlcode} An positive answer should contain: \begin{out} \$answer_hr = { 'msg' => '', 'cmd' => 'login', 'cmdres_r' => { 'ttl' => '20', 'ticket' => '1559cc7c463af4a5a28586e931fbf744', 'login' => 'cipadmin' }, 'status' => 'TRUE', 'header_hr' => { 'cipux_version' => '3.4.0.0', 'server_key' => '', 'server_cred' => '', 'gmt_time' => '1196466106', 'server_version' => '3.4.0.0', 'server_name' => 'cipux_rpcd', 'rpc_version' => '2.0' }, 'type' => 'href', 'ticket' => 'dummy', 'login' => 'cipadmin' }; \end{out} Be aware that you have to grab the \lstinline|cmdres_r| ticket not the payload ticket! The example script \verb|expl_rpc_session| contains a \verb|login| call. \subsection{logout} \api{logout}{logout}{yes}{yes}{empty}{hash reference}{empty} There is nothing much to tell about the \verb|logout| call. If it is successfully issued it returns ``\verb|TRUE|'' as its status back. The example script \verb|expl_rpc_session| contains a \verb|logout| call. \begin{out} 'status' => 'TRUE', \end{out} \subsection{ttl} \api{ttl}{ttl}{yes}{yes}{empty}{hash reference}{ttl} The relevant part of the answer: \begin{out} 'cmdres_r' => {'ttl' => '20'}, \end{out} See section \ref{call_ttl_as_ttl} on page \pageref{call_ttl_as_ttl} for an example. \subsection{session} \label{session} \api{session}{session}{yes}{yes}{empty}{hash reference}{ticket or empty} The session call can be used in two ways with the same syntax. First it can be used to check if the session was still valid. Second the call gives you a new ticket in the \lstinline|cmdres_r|, so you can use the call to extent the session. The best is to uses both ways together. If the session is not valid, you should examine the \lstinline|msg| payload field for more information. In that case \lstinline|cmdres_r| is empty. \begin{out} \$answer_hr = \{ 'msg' => 'The ticket check was not successful. This could have several reasons: a timeout, logout, ... Please log in again. is_ticket_bad: (ticket is bad: time login mismatch)', 'cmd' => 'session', 'cmdres_r' => {}, 'status' => 'FALSE', 'header_hr' => { 'cipux_version' => '3.4.0.0', 'server_key' => '', 'server_cred' => '', 'gmt_time' => '1196466106', 'server_version' => '3.4.0.0', 'server_name' => 'cipux_rpcd', 'rpc_version' => '2.0' }, 'type' => 'href', 'ticket' => '1559cc7c463af4a5a28586e931fbf744', 'login' => 'cipadmin' }; \end{out} If the session is still a valid session a new ticket will be given via the \lstinline|cmdres_r| reference. In this case a hash reference with the key \lstinline|ticket|. \begin{out} 'cmdres_r' => { ticket => '285c9699e3f664fbfce5d04e6d0b98e0' }, \end{out} You can grab this for example with this lines of Perl code. \begin{perlcode} my $new_ticket = undef; if ( exists $answer_hr->{cmdres_r}->{ticket} and defined $answer_hr->{cmdres_r}->{ticket} and $answer_hr->{cmdres_r}->{ticket} ) { $new_ticket = $answer_hr->{cmdres_r}->{ticket}; } \end{perlcode} The old session ticket, which you send to the server will also given back via the \lstinline|ticket| key in the payload of the answer. To make the distinction clear you could grab this with this lines of Perl code. \begin{perlcode} my $old_ticket = undef; if ( exists $answer_hr->{ticket} and defined $answer_hr->{ticket} and $answer_hr->{ticket} ) { $old_ticket = $answer_hr->{ticket}; } \end{perlcode} Sometimes things get wrong. One real user scenario might be that the user waited too long and the session expired or might get wrong because of other reasons. In this case the answer from the server would look like this. Non important keys where omitted. \begin{out} 'msg' => 'Your ticket is not valid! This can have serveral reasons: (1) the\ ticket expired. (2) an error in programming (3) you never logged in', 'cmdres_r' => {}, 'status' => 'FALSE', 'ticket' => 'test', 'login' => 'test' \end{out} So you can expect to get a status of \verb|FALSE| and an empty \lstinline|cmdres_r|. The \verb|msg| field might give a hint what is wrong. For now it refers to 3 problems. This might changed in the future to be more precise. This message might also be translated into other languages. For an example see the script \verb|expl_rpc_session|, it contains a \verb|session| call. \subsection{task} \api{task}{name of CipUX::Task command}{yes}{yes}{depends}{hash reference}{depends} The RPC scope ``task'' is the most used part of the \CipUX{} XML-RPC server calls. Because the \verb|task| scope has a lot of sub-commands we will start with a simple example. We assume that you have some users in the role ``student'' or ``students'' on your system. To list those you have to use the sub-command \verb|cipux_task_list_student_accounts|. Just to show you the expected output you can use the CipUX::Task layer directly as root. \begin{shell} cipux_task_client -t cipux_task_list_student_accounts \end{shell} If you have students on you system you might get a similar output like this: \begin{out} students bilbo frodo mytest \end{out} Here ``students'' is the role and the members of this role are: ``bilbo'', ``frodo'' and ``mytest''. Section \ref{full_task_call_example} on page \pageref{full_task_call_example} contains a full example what parameter are used and what the expected output looks like if you code this by hand. If you re-program this call by hand it can get longly homework. Programming ping, login, task and then logout will take a long time. To make this shorter in Perl you can use the helper module \verb|CipUX::RPC::Client|. If you use an other language then Perl have a look at the source code and comments of the \lstinline|sub extract_data_for_tpl { ... }| subroutine of \verb|CipUX::RPC::Client|. This was originally written for \CAT{} to give it a helper routine to parse the output of an XML-RPC answer. However you probably should implement a similar or even better routine in your application. Here comes a full fledged \verb|CipUX::RPC::Client| example with the XML-RPC scopes ping, login, task (\verb|cipux_task_list_student_accounts|) and of course logout. \perlcodefile[label={expl\_rpc\_task\_list}]{../example/bin/expl_rpc_task_list} If you use this sample program you will get output like this. \begin{out} expl_rpc_task_list$ Enter login: cipadmin expl_rpc_task_list$ Enter password: Students on the system: bilbo frodo mytest \end{out} One short word on the server response. The server response depends on the sub command and the data structure which come from the storage layer. This is the data which the \verb|cipux_task_list_student_accounts| sub-command returns. The most relevant parts of this answer are \lstinline|'ltarget'| and \lstinline|'cmdres_r'| \begin{out} \$answer_hr = { 'msg' => '', 'ltarget' => 'memberUid', 'cmdres_r' => { 'students' => { 'cn' => [ 'students' ], 'memberUid' => [ 'bilbo', 'frodo', 'mytest', ] } }, 'cmd' => 'cipux_task_list_student_accounts', 'status' => 'TRUE', 'login' => 'cipadmin', 'problem' => 0, 'header_hr' => { 'cipux_version' => '3.4.0.0', 'server_key' => '', 'server_cred' => '', 'gmt_time' => 1260715448, 'server_version' => '3.4.0.0', 'server_name' => 'cipux_rpcd', 'rpc_version' => '2.0' }, 'type' => 'HASH', 'ticket' => '240bf8a03ed86a8f5b762f9cccdce73f' }; \end{out} The expression \begin{perlcode} my $d_hr = $rpc->extract_data_for_tpl( { answer_hr => $a_hr } ); \end{perlcode} would convert this to the following quite similar output. \begin{out} \$d_hr ={ 'tpl_data_ar' => [ { 'cn' => 'students', 'memberUid' => 'bilbo, frodo, mytest' } ] }; \end{out} You can use this directly. And the key \verb|ltarget| with its value \lstinline|'memberUid'| will be provided by the server response. The trick the former mentioned subroutine does is parse every output and transform this to a unified format. The additionally parameter \verb|use_ltarget| \begin{perlcode} my $d_hr = $rpc->extract_data_for_tpl( { answer_hr => $a_hr, use_ltarget => 1, } ); \end{perlcode} will produce a very different data structure. \begin{out} \$d_hr = { 'ltarget' => 'memberUid', 'tpl_data_ar' => [ { 'memberUid' => 'bilbo' }, { 'memberUid' => 'frodo' }, { 'memberUid' => 'mytest' }, ] }; \end{out} This look somewhat bigger, but is automatically processable. \subsubsection{Taxonomy} This section deals with logic behind task scope sub-command names. You can derive the mandatory parameter(s) from this logic. \rowcolors[\hline]{2}{blue!25}{gray!25} \arrayrulecolor{black!75!gray} \begin{tabular}{l|l|l} \textbf{sub-command schematic} & \textbf{param\_hr} & \textbf{return scope} \\\hline $*$\_add\_(M)\_to\_(G) & object=(G), value=(M)& \\ $*$\_change\_(X)\_(L) & object=(X), value=(L) & \\ $*$\_create\_(X) & object=(X) [1] & \\ $*$\_deregister\_(X) & object=(X) & \\ $*$\_destroy\_(X) & object=(X) & \\ $*$\_disable\_(X) & object=(X) & \\ $*$\_enable\_(X) & object=(X) & \\ $*$\_list\_(A)s & &all objects \\ $*$\_list\_(M)\_of\_(G) & object=(X) &all (M) \\ $*$\_obtain\_(X)\_(L) & object=(X) &one attribute \\ $*$\_register\_(A) & object=(X) & \\ $*$\_remove\_(M)\_from\_(G) & object=(G), value=(M)& \\ $*$\_retrieve\_all\_(A)\_(L1)\_..\_(Ln) & object=(X) &all attributes\\ %$*$\_retrieve\_from\_(A)_all\_(L1)\_..\_(Ln) & object=(X) $*$\_search\_all\_(L) & &all attributes\\ $*$\_sum & [2] &one sum \\ \end{tabular} \begin{footnotesize} \begin{verbatim} *: cipux_task [1]: require sometimes some other parameter depending on the object require no additional parameter for *_account or *_share [2]: only for testing (A): object is coded in task name (G): group object, line *_account or *_share (L): LDAP attribute codes as English name (memberUid -> member) (M): member, client (Y): member, client (variable) (X): ID, which is a variable ID to the object name (variable) \end{verbatim} \end{footnotesize} %disfemist %eufemist \subsubsection{Object call parameter} \label{object_call_parameter} The \lstinline|'object'| parameter is an abstract \lstinline|$param_hr| hash key to define the name of the thing the sub-command is operating on. As you learn in the RPC \verb|task| scope sub-command taxonomy, not every sub-command do need an object, but some do. If the sub-command needs an object it can be provided like this: \begin{perlcode}[commandchars=\\\[\],label={Object parameter}] my $param_hr = { \fbox[object] => $some_object_scalar, }; \end{perlcode} The example script \verb|expl_rpc_task_create_destroy| plays with this object parameter. It creates a student account and delete it thereafter. \perlcodefile[label={expl\_rpc\_task\_create\_destroy}]{../example/bin/expl_rpc_task_create_destroy} The script requires a parameter, the name of the account which should be created. In this case \verb|mytestobj1| was provided as an object. \begin{shell} perl expl_rpc_task_create_destroy mytestobj1 expl_rpc_create_destroy$ Enter login: cipadmin expl_rpc_create_destroy$ Enter password: Students on the system: bilbo frodo mytest Students on the system: bilbo frodo mytest mytestobj1 Students on the system: bilbo frodo mytest \end{shell} \subsubsection{Value call parameter} \label{value_call_parameter} The \lstinline|'value'| parameter is an abstract \lstinline|$param_hr| hash key to define the data for a given \lstinline|'object'|. Like \verb|--object frbeutlin| or \verb|-o frbeutlin| and \verb|-x value=Frodo| giving on the command line the value of \verb|-x| are most likely referring to the \emph{first name} of the account \verb|frbeutlin|. In this terms the meaning of the abstract \verb|value| is determined by the sub-command name. As you learned in the RPC scope \verb|task| taxonomy about sub-commands, not every sub command do need a \verb|value|, but some do. If the sub-command needs a \verb|value| it can be provided like this on the command line: \begin{shell} cipux_task_client -t cipux_task_add_member_to_role_account \ -o tutor \ -x value=frbeutlin \end{shell} Keep in mind that the role \emph{tutor} might be different on your system. The same sub-command looks with its parameters in code like: \begin{perlcode}[commandchars=\\\[\],label={Abstract value parameter hash}] 'param_hr' => { 'object' => 'tutor' \fbox['value'] => 'frbeutlin', }, \end{perlcode} \subsubsection{Other call parameters} In most cases there are performed additional calculations on the abstract \verb|object| and \verb|value| parameters. Therefore it is the best practice to provide those parameters as abstract hash key. This was introduced in section \ref{object_call_parameter} and \ref{value_call_parameter} at page \pageref{object_call_parameter} and \pageref{value_call_parameter}. %The pro is that you do not have to care when you write clients which use task %of the form \verb|cipux_task_add_(Y)_to_(X)| where as Y might be [client, %member] and X might be a basic \verb|user_account| oder \verb|group_share| or %netgroup. This new ``value'' parameter can be used by all which uses %CipUX::Object \verb|change_object_attribute_action|. This should work for tasks %like: % %\begin{verbatim} % - cipux_task_add_(Y)_to_(X) % - cipux_task_remove_(Y)_from_(X) %\end{verbatim} It is of course possible to provide other parameters too, not just \verb|object| and \verb|value|. \begin{perlcode}[commandchars=\\\[\],label={Create call with other parameters}] my $pay_hr = { header_hr => $header_hr, login => $login, ticket => $ticket, cmd => 'cipux_task_create_student', param_hr => { object => 'bibeutin', \fbox[cipuxFirstname] => 'Bilbo', # cipux.schema \fbox[cipuxLastname] => 'Beutlin', # cipux.schema \fbox[userPassword] => $new_password, # core.schema \fbox[homeDirectory] => '/home/cipux0/bibeutlin', # nis.schema }, }; \end{perlcode} However, this example would work only on systems where those LDAP attributes are defined. In this example \verb|cipuxFirstname| and \verb|cipuxLastname| is defined in the cipux.schema. If you use those LDAP attributes directly you make your application dependent on the existence and usage of those schemata. If possible try to avoid the direct use of LDAP attributes. Unfortunately \CipUX{} is not providing more abstract attributes. It would be a nice feature to have a dispatch list for this in some future version to avoid using LDAP attributes directly. Foreseen attributes might be \verb|firstname|, \verb|lastname|, \verb|mailaddress|, ... But there is limited solution for this problems in the current version, which will be introduced now. The above problem could be avoided if you call 4 task scope sub-commands which gives you the desired level of abstraction: \begin{LstWs} \item \verb|cipux_task_create_student_account| \item \verb|change_user_account_firstname| \item \verb|change_user_account_lastname| \item \verb|change_student_account_password| \end{LstWs} If you use this sub-commands you could avoid using discrete LDAP attributes. On other thing is that you should choose the smallest object scope as possible. So choosing the next list of commands is a little bit better, because your client would need less access rights to do it. \begin{LstWs} \item \verb|cipux_task_create_student_account| \item \verb|change_student_account_firstname| \item \verb|change_student_account_lastname| \item \verb|change_student_account_password| \end{LstWs} If some sub-commands are missing in in you installation or in \CipUX, this sub-commands can be added to the configuration file \verb|/etc/cipux/cipux-task.perl| or \verb|/usr/share/cipux/etc/cipux-task.perl|\footnote{You can find example sub-commands in this file. You can add something or modify this. However this is the location of the CipUX::Task boostrap configuration and will be overwritten when you install a new version.} locally or better you write just your additional sub-commands in a newly created file with the ending \verb|.perl| under \verb|/usr/share/cipux/etc/cipux-task.d/your_name.perl|. This will be additionally read in by the \CipUX{} configuration space. The best way is of course to share your new sub-commands on the mailing list cipux-devel. This will give you feedback. You might get hints on improvement or your commands will even be be included in the next CipUX-Task release. \subsubsection{More then one call parameter} It is possible to add other attributes to a call. However this version of CipUX do not track the possibility if such an attribute can be used or not. The LDAP server in the last instance will decide what is possible and what not. To get an idea what is possible it is a good idea to look at all cipux-task.perl configuration files. But also additional attributes are possible. It is planned that an introspective command of the RPC will be written to support a query, for each object, to see what is possible. For now the LDAP schemata are the reference. Using the \verb|cipux_task_client| is also a quick method to find out what is possible when you add the \verb|--debug| switch on the command line. \begin{shell} cipux_task_client -t cipux_task_create_teacher_account -o boromir \ -x cipuxFirstnanme=Boromir -x cipuxLastname='Son of Denethor II.'\ --debug \end{shell} This is equivalent to the following \lstinline|$param_hr| perl code. \begin{perlcode} \$param_hr = { object = 'boromir', cipuxFirstname = 'Boromir', cipusLastname = 'Son of Denethor II.', }; \end{perlcode} It is not possible to show the hole output here, but you will get an idea, what additional LDAP attributes are possible for the \lstinline|$param_hr| keys if you read the output. This and the next outputs are slightly modified to fit on the page. \begin{out} ... CipUX::Storage::add_node <461>: -> found obj.Class: objectClass CipUX::Storage::add_node <470>: -> found obj.Class name: posixAccount CipUX::Storage::add_node <486>: --> mandatory attr: uid CipUX::Storage::add_node <486>: --> mandatory attr: gidNumber CipUX::Storage::add_node <486>: --> mandatory attr: homeDirectory CipUX::Storage::add_node <486>: --> mandatory attr: uidNumber CipUX::Storage::add_node <486>: --> mandatory attr: cn CipUX::Storage::add_node <499>: --> auxiliary attr: description CipUX::Storage::add_node <499>: --> auxiliary attr: gecos CipUX::Storage::add_node <499>: --> auxiliary attr: loginShell CipUX::Storage::add_node <499>: --> auxiliary attr: userPassword ... \end{out} This section for example logs the output of the subroutine \verb|add_node|. It tells in details which attributes are mandatory or optional. Keep in mind that \verb|cipux_task_client| will throw an exception when a mandatory option is missing. This options are mandatory in terms of LDAP and sometimes not mandatory for the client to provide. So you can imagine that \CipUX{} tries hard to automatically calculate many mandatory and auxiliary values. Some of them can be overwritten on the fly. A better approach might be to overwrite them in a second call or they should be changed globally in a configuration file if you intend to always overwrite them. This is because a automatically calculated value might be used as an input to a second automatically calculated value. So you have to try or to understand what you do and what effect do this have. If unsure changing later is the better approach. \begin{out} 2009-12-17+23:36:24 CipUX::Storage::add_node <407>: BEGIN 2009-12-17+23:36:24 CipUX::Storage::add_node <408>: > obj: boromir 2009-12-17+23:36:24 CipUX::Storage::add_node <409>: > type: cipux_account.user 2009-12-17+23:36:24 CipUX::Storage::add_node <410>: > attr_hr: $VAR1 = { 'cipuxHardQuota' => 200000, 'cipuxIsAccount' => 'TRUE', 'cipuxInternetStatus' => 'accept', 'cn' => [ 'Boromir Son of Denethor II.' ], 'cipuxLastname' => [ 'Son of Denethor II.' ], 'uidNumber' => [ 10157 ], 'cipuxRemark' => 'CipUX task layer', 'cipuxSkeletonUid' => [ 'none' ], 'cipuxMail' => [ 'boromir@tjener' ], 'cipuxStatus' => 'idle', 'cipuxRole' => 'teachers', 'gecos' => [ 'boromir' ], 'userPassword' => [ '{crypt}GyDkuuhqJi/rU' ], 'cipuxSoftQuota' => 100000, 'uid' => [ 'boromir' ], 'homeDirectory' => ['/skole/tjener/home0/boromir'], 'objectClass' => [ 'posixAccount', 'top', 'shadowAccount', 'imapUser', 'cipuxAccount' ], 'gidNumber' => [ 10157 ], 'cipuxFirstname' => 'Boromir', 'cipuxCreationDate' => [ '2009-12-17T23:36:23' ], 'mailMessageStore' => ['/var/lib/maildirs/boromir'], 'loginShell' => '/bin/bash' }; \end{out} If you read further in the debug output you can find hole hash trees for LDAP object like this one. But be aware of just copying those structures to your XML-RPC client, because this is only on small part of what happens if a user account is created. \subsubsection{Exceptions} If you create a user account (eg. ``frbeutlin'') and try to create this twice which is of course not possible you will most likely get an exception similar like this one. \begin{out} (EXCEPTION) Can not add entry \ [cn=frbeutlin,ou=Group,dc=skole,dc=skolelinux,dc=no]! \ (Already exists) \end{out} This exception are triggered from the object layer and are given back to the client over the XML-RPC server. You have to catch those exceptions. \subsubsection{Access to a task} Every task hast its own access check when requested via RPC. If we for example code this \lstinline|'ttl'| call: \begin{perlcode}[commandchars=\\\[\],label={Call ttl as a task}] my $pay_hr = { header_hr => $header_hr, login => 'cipadmin', ticket => $some_valid_ticket, cmd => \fbox['ttl'], param_hr => {}, }; my $answer_hr = $server->call( \fbox['task'], $pay_hr); \end{perlcode} Then we will be astonished because \verb|cipadmin| has \emph{no right} to execute the task \lstinline|'ttl'| and the answer will be harsh. \begin{out} \$answer_hr = { 'msg' => 'No access for [cipadmin] to [ttl]', 'ltarget' => 'NULL', 'cmd' => 'ttl', 'cmdres_r' => {}, 'status' => 'FALSE', 'header_hr' => $sever_header_hr, 'type' => 'href', 'ticket' => 'c73ce0812fd68a2345254634dab64e24', 'login' => 'cipadmin' }; \end{out} We learn earlier that the \lstinline|'ttl'| command can be accessed by everyone. Why can the access then be denied? Well, a common mistake is to think that \lstinline|'ttl'| is a \CipUX{} task (to be correctly a task scope sub command). It is not! The ``\verb|ttl|'' is a RPC scope by itself. The call which generates this ``access denied'' called the ``\verb|task|'' scope with the cmd \lstinline|'ttl'|. Instead we should call the \lstinline|'ttl'| command with the right scope \verb|ttl|. \label{call_ttl_as_ttl} \begin{perlcode}[commandchars=\\\[\],label={Call ttl as a ttl}] my $pay_hr = { header_hr => $header_hr, login => 'cipadmin', ticket => $some_valid_ticket, cmd => \fbox['ttl'], param_hr => {}, }; my $answer_hr = $server->call( \fbox['ttl'], $pay_hr); \end{perlcode} This will give us the correct answer from the server. \begin{out} \$answer_hr = { 'msg' => '', 'problem' => '0', 'ltarget' => 'NULL', 'cmd' => 'ttl', 'cmdres_r' => { 'ttl' => '600' }, 'status' => 'TRUE', 'header_hr' => $HEADER_HR, 'type' => 'href', 'ticket' => '1223457de8afce5c0bcae9092833d082', 'login' => 'cipadmin' }; \end{out} This was an example of calling an unknown sub-command like \lstinline|'ttl'| in the RPC scope \verb|task| and getting an ``access denied'' as an answer. If you are calling a real \verb|task| sub-command (for example \lstinline|'cipux_task_list_student_accounts'|) you have the chance that the user has access to this. Because \emph{cipadmin} is in the \emph{admin} group he should have access to all \verb|task| sub-commands. For other users this depends on the role based access control (RBAC) module which can not be explained here in detail. Basically you can assume that if the RPC client is registered and has registered its \verb|task| sub-commands and a group is assigned to that client and a user is assigned to that group the user has access to that tasks. \section{Toolbox} This section describes some programs coming with the XML-RPC server which might be useful for testing and debugging. \subsection{cipux\_rpcd} The most important tool is the server by itself. Normally started you might get some information as log messages through you system log facility. However this is limited. To get more information the sever has a debug mode. Of course you have to stop the running server for this. \begin{shell} /usr/sbin/cipux_rpcd --debug \end{shell} This will print a lot of messages on the console! Of course this is not a good way to operate that server in production mode - it slows everything down, but it helps developing a XML-RPC client or to see if something is wrong. \CipUX{} uses an advanced logging facility Log::Log4perl. This was developed for Java and adopted for Perl. More information can be found at CPAN. You can find the configuration in log4perl.conf. Be aware that \CAT{} uses its own file. Sometimes this will generate too much messages. You can use the Log::Log4perl configuration to switch on and off messages, filter them, redirect them. In principle you can restrict logging to certain \CipUX{} Perl modules or even subroutines. After the logging configuration has changed the server has to be restarted before it takes effect. \subsection{cipux\_rpc\_list} This script uses the same configuration space as the XML-RPC server. Its only duty is to list all calls and all sub-commands in a long list. Even though this a good thing to have in this output calls and sub-commands are not distinguished. Therefore this is a very simple method for checking what is available in terms of shell scripts. If the server gets an introspection mechanism in the future this command should be enhanced/replaced accordingly. \begin{shell} cipux_rpc_list|grep -v cipux_task \end{shell} Since all sub-commands in the RPC scope \verb|task| starting with \verb|cipux_task| the trick with the \verb|grep -v| do the trick to list all scopes. \begin{out} ping version sum login logout session ttl task \end{out} \subsection{cipux\_rcp\_test\_client} The test client can be used to test a locally installed \CipUX\ XML-RPC server. The usage is straight forward and self explanatory. Prepare yourself with the login of \verb|cipadmin| and the password. The test script will create and delete some test accounts. %\input{cipux_rpc_test_client} The \verb|cipux_rcp_test_client| runs several RPC calls and try to figure out if the answers are valid or not. It can therefore used to test if the XML-RPC behaves correctly in some ways. Because the \verb|cipux_rcp_test_client| execute real tasks like creating and deleting users and groups on the system there is the possibility to delete accounts that should not be deleted. Even if this is not very likely it is more likely to corrupt the LDAP database in terms of data validity and because of this risk it is advised to uses this command after a LDAP backup and not on productive systems unless you can assure that the LDAP data before running and after running is the same in terms of validity. So keep in mind until someone volunteers to enhance it, this program is not bullet-proof. \begin{shell} cipux_rcp_test_client \end{shell} This will outputs hundreds of lines. The last lines might look like this ones: \begin{out} ~ Test 0305: CipUX::Task 03 create netgroup exec SUCSESS Test 0306: CipUX::Task 04 create netgroup list SUCSESS Test 0307: CipUX::Task 05 create netgroup result SUCSESS Test 0308: CipUX::Task 06 add client to netgroup exec SUCSESS Test 0309: CipUX::Task 07 add client to netgroup list SUCSESS Test 0310: CipUX::Task 08 add member to netgroup result SUCSESS Test 0311: CipUX::Task 09 erase client 's of netgroup list SUCSESS Test 0312: CipUX::Task 10 destroy netgroup exec SUCSESS Test 0313: CipUX::Task 11 destroy netgroup list SUCSESS Test 0314: CipUX::Task 12 destroy netgroup result SUCSESS ---------------------------------------------------------------------- SUCSESS: 314 FAILURE: 0 ---------------------------------------------------------------------- \end{out} An other test of the script is also very useful: testing if the LDAP database is more or less the same before and after this script was executed. To perform this test you have to be root or at least you have to have the execution rights for slapcat. \begin{shell} slapcat > before ~$ cipux_rpc_test_client ~$ slapcat > after ~$ diff --ignore-matching-lines '^modifyTimestamp' \ --ignore-matching-lines '^entryCSN' before after \end{shell} If you have no output of the \verb|diff| command the LDAP is more or less the same as before. \subsection{cipux\_rpc\_test\_repetition} The \verb|cipux_rpc_test_repetition| script can be called simply \begin{shell} cipux_rpc_test_repetition --time 2000 \end{shell} It will run for the specified time in seconds. To test the default ticket life span of 1200 seconds a value larger then 1200 for example 1800 (30 minutes) should be used. For 1 minute we get this output at the end of the job. \begin{out} ======================================================================== pay ticket 13e777a207fb292f3a1e3d94d8fcedc9 default channel ticket 13e777a207fb292f3a1e3d94d8fcedc9 cipux_task_list_user_accounts at 1260214026 status TRUE \ (13e777a207fb292f3a1e3d94d8fcedc9) pay ticket 13e777a207fb292f3a1e3d94d8fcedc9 default channel ticket adf89d46c9e167b5758adbaf405110ac explicit channel ticket adf89d46c9e167b5758adbaf405110ac ======================================================================== pay ticket adf89d46c9e167b5758adbaf405110ac default channel ticket adf89d46c9e167b5758adbaf405110ac cipux_task_list_user_accounts at 1260214026 status TRUE \ (adf89d46c9e167b5758adbaf405110ac) pay ticket adf89d46c9e167b5758adbaf405110ac default channel ticket d9f3a0acce41b72a5c4dbff23f2e8c9c explicit channel ticket d9f3a0acce41b72a5c4dbff23f2e8c9c Summary: start at: 1260213967 stop at: 1260214027 seconds: 60 true : 291 false : 0 \end{out} \section{CAT - other \CipUX{} XML-RPC clients} A CipUX Administration Tool (\CAT) do not have to be a XML-RPC client. At the time of this writing all of them are. One reason is that a XML-RPC clients for \CipUX{} to not have to have root rights to access the XML-RPC server. Some of the existing clients are Web clients, where it is out of question running this clients with root rights. Another reason is that \CipUX{} XML-RPC hide most of the complexity of the storage layer from the client and that the objects which are created are the same among all clients. Because \CipUX{} functions as an abstraction layer between the clients and the operation system additional features can be implemented and used among all clients. This section tries to summarize all \CAT s to give an overview about the current development as well as a starting point regarding the search for further informations about different clients. The order of this sections represented the age of the tool. \subsection{CAT-Web} The development of CAT-Web started as CAT in the year 2000 when there was a need for a \CipUX{} Administration Tool. The first university internal release 1.0 was a standalone CGI program for the apache web server. For the version 2.0.0 CAT was converted to a standard webmin module and used up to version 3.2.16 with root rights. This approach was obsolete when webmin was expelled from Debian. The name changed from CAT ot CAT-Web and is basically a rewrite from scratch. Some pre-releases that already uses the XML-RPC server where made public in 2007 - 2009. The next version 3.4.0.0 will be released ending 2009 or beginning 2010 and is a CGI application for apache (and probably other web servers) written in object orientated Perl that supports template driven skinning. Home page: \href{http://www.cipux.org}{http://www.cipux.org} (English) \subsection{CipuxPHP} %my text %The starting point of this software where actually %before the CipUX XML-RPC server by itself where %developed. The first version was developed by Benoit Sonntag %in a real time programming session with the French %Skolelinux Team in Forbach, France in July 2005. %Jean-Charles Siegel took over the development in %2006 at the great summer meeting at ZZZ-Mines and %developed the second version which was able to access %CipUX 3.2.6, which was deployed at the French Add-On %CD for Skolelinux. The third major version was %developed by Jean-Charles Siegel and Christophe %Gossen for an university project that supports a %non official release of CipUX. The forth major version %development started again Jean-Charles Siegel %together with the French Skolelinux Team in 2009 for %CipUX 3.4.0.x. This is still under development. %JCS improvements % private mail 2009-12-11 (license GPLv2 or later 2009-12-14) CipuxPHP, is a web interface for managing users on Skolelinux. It's a module for the CMS Moodle, but it was also written to work in a stand-alone mode in the case of Moodle isn't installed. The starting point of this software where actually before the \CipUX{} XML-RPC server by itself where developed. \textbf{2005:} The first version was developed by Benjamin Sonntag in a real programming session with the French Skolelinux Team in Forbach, France in July 2005. \textbf{2006:} Jean-Charles Siegel took over the development in Summer 2006 at the great summer meeting at Carreau Wendel's – Mines Museum (Petite-Rosselle, France) and developed the second version that was able to access \CipUX{} 3.2.6, which was deployed at the French Add-On CD for Skolelinux. A second big dev-camp in Extramadura's Summer University (Spain's area) was the theater of an important development of CipUX. \textbf{2007:} The third major version was developed by Jean-Charles Siegel and Christophe Gossen for an university project that supports a non official release of \CipUX{} in 2007. It was written in PHP object, using templates to display the web pages. \textbf{2010:} The forth major version development started again Jean-Charles Siegel together with the French Skolelinux Team in 2010 for \CipUX{} 3.4.0.x. This is still under development. The goal of cipuxPHP is to \emph{manage easily} the pupils in Skolelinux. Create, modify, delete them. But also manage classes (considered as groups) and move pupils from one class to an other. Home page: \href{http://wiki.skolelinux.fr/CipuxXmlRpc}{http://wiki.skolelinux.fr/CipuxXmlRpc} (French) \begin{flushright} \footnotesize(Jean-Charles Siegel) \end{flushright} \subsection{CipUX-Passwd} CipUX::Passwd is a Perl module and a command line script that can be used as an CipUX XML-RPC client to set once own password. It was developed by Christian K\"ulker in 2009 and released as CipUX-Passwd-3.4.0.2. \subsection{CATweasel} %my text %This project was started by Jochen Breuer in 2009 with the aim to develop a %full featured CipUX Administration Tool with a good usability. % JB text, mail on cipux-devel 2009-12-11 (license GPLv2 or later 2009-12-14) The project was started in early 2009 for the RLP-Skolelinux project. Main contributor is the ed-media GmbH from Kaiserslautern Germany. Debian maintainer for CATweasel is Jonas Smedegard. Many thanks for that Jonas! CATweasel was started because there was a steady demand for more interactivity in the web UI for CipUX. Klaus Knopper then decided to contract the ed-media GmbH to start a new project that should establish a basis on a modern web framework to accomplish that. That was the birth of CATweasel. At the moment there is no fancy website with lots of screenshots. Just a boring Wiki with docu and the repository on bitbucket:\\ \href{http://bitbucket.org/edmedia/catweasel/wiki/Home}{http://bitbucket.org/edmedia/catweasel/wiki/Home} \begin{flushright} \footnotesize(Jochen Breuer) \end{flushright} CipUX-RPC-3.4.0.9/doc/tecdoc/cipux_xml_rpc_server_and_client.tex000444001750001750 540111432067074 25407 0ustar00ckuelkerckuelker000000000000% +=========================================================================+ % || Begin || % +=========================================================================+ \documentclass[a4paper,twoside,12pt,cleardoubleempty,footsepline,headsepline,idxtotoc]{scrartcl} % +=========================================================================+ % || Preamble || % +=========================================================================+ \include{tecdoc_preamble} % +=========================================================================+ % || Begin Document || % +=========================================================================+ \begin{document} % +=========================================================================+ % || Title || % +=========================================================================+ \title{\smaller The \CipUX{} XML-RPC Server and Client\\ \small Communication Across Boundaries } \date{Version 3.4.0.9} \author{Christian K\"ulker} %\begin{fullpage} \maketitle %\end{fullpage} % +=========================================================================+ % || Abstract || % +=========================================================================+ \begin{abstract} This document addresses the basic {\CipUX} XML-RPC server and client communication specification and its usage. The server API is explained in detail and examples for accessing the server are given in Perl. Other XML-RPC client developers should be in a position to acquire the important information through the given examples to seamlessly develop own applications. Sections about tools, debugging hints and existing free open source XML-RPC client projects complete this paper. \end{abstract} % +=========================================================================+ % || Table of Content || % +=========================================================================+ %\begin{fullpage} \tableofcontents %\end{fullpage} \newpage % +=========================================================================+ % || Content || % +=========================================================================+ \include{rpc} % +=========================================================================+ % || End Document || % +=========================================================================+ \end{document} CipUX-RPC-3.4.0.9/doc/tecdoc/fr000755001750001750 011432067074 16515 5ustar00ckuelkerckuelker000000000000CipUX-RPC-3.4.0.9/doc/tecdoc/fr/php.txt000444001750001750 1443611432067074 20232 0ustar00ckuelkerckuelker000000000000Auteurs : Ludovic Kiefer (ludovic@skolelinux.fr) Jean-Charles Siegel (jeancharles@skolelinux.fr) Date : 21 mars 2010 Système : debian-edu etch debian-edu lenny debian-edu squeeze Communication entre un client et un serveur XML-RPC - CipUX-cat-moodle ---------------------------------------------------------------------- 1. Pré-requis Il faut avoir d'installé le serveur mysql. La dernière version présente dans vos dépôts sera la bonne. C'est un méta-paquet appelé "mysql-server". # aptitude install mysql-server Il vous sera demandé un mot de passe pour le super utilisateur de mysql. Installez maintenant les paquets pour php5. A savoir : * php5 : paquet de base pour php5 * php5-mysql : paquet pour la connexion entre php5 et mysql * php5-curl : paquet pour activer le cryptage SSL de php # aptitude install php5 php5-mysql php5-curl Maintenant vous pouvez installer moodle et le theme Skolelinux (debian-edu) de moodle. # aptitude install moodle moodle-debian-edu-theme Il vous sera demandé le serveur web à utiliser. Choississez apache2. De même vous devrez choisir le serveur de base de données. Comme nous avons installé mysql, choississez le. L'adresse demandée pour la connexion à la base de données est par défaut localhost. Laissez cette adresse. Par contre il vous sera ensuite demandé de saisir le nom du super-utilisateur de Mysql (si vous avez choisi mysql), celui- ci est "root" et le mot de passe est celui que vous avez saisi lors de l'installation du serveur Mysql. Maintenant il faut créer un nom d'utilisateur pour moodle qui servira dans Mysql. Par défaut celui-ci est "moodle". Laissez-le. Vous devez choisir un mot de passe pour cet utilisateur, choississez celui qui vous plaît. Normalement vous ne devriez plus en avoir besoin. IL faut évidemment avoir un serveur XML-RPC déjà d'installé, et particulièrement le serveur CipUX-XML-RPC. (voir avec Christian Külker) 2. Développer un client XML-RPC en PHP pour CipUX 2.1. Le fichier de configuration /** Server IP address or DNS name */ ifdefine("CIPUX_RPC_HOST","localhost"); /** Server TCP Port */ ifdefine("CIPUX_RPC_PORT","8001"); /** Server PATH */ ifdefine("CIPUX_RPC_PATH","/RPC2"); /** Should we put plenty of debug lines on stdout ? (default no) */ ifdefine("CIPUX_RPC_DEBUG",2); /** Socket timeout in seconds */ ifdefine("CIPUX_RPC_TIMEOUT",10); /** Transport can be one of http, https, http11 */ ifdefine("CIPUX_RPC_TRANSPORT","http"); /** Cookie name send to the user (if you request for automagic cookie sending) */ ifdefine("CIPUX_RPC_COOKIE_NAME","COOKIE_CIPUX"); Il indique où communiquer avec le serveur XML-RPC. Donc ici on communique à l'adresse : http://localhost:8001/RPC2 2.2. Librairies Il faut utiliser les librairies xmlrpc.inc et xmlrpcs.inc. Elles contiennent toutes les fonctions de bases pour communiquer avec un serveur XML-RPC. La librairie cipux_xml.class.php est spécialement faite pour communiquer avec un serveur XML-RPC. Elle est INDISPENSABLE. 2.3. Communication avec le serveur 2.3.1. Construction du fichier xml à envoyer. On va prendre une fonction toute simple. La fonction PING. Extrait du code perl faisant appelle à la fonction PING créée par Chrisitan Külker. [...] my $header_hr = { cipux_version => '3.4.0.0', client_name => 'expl_rpc_ping', client_version => '0.1', rpc_version => '2.0', client_key => '', client_cred => '', gmt_time => time, }; my $pay_hr = { header_hr => $header_hr, login => 'dummy', ticket => 'dummy', cmd => 'ping', param_hr => {}, }; [...] eval { $answer_hr = $server->call( 'ping', $pay_hr ); }; Vous pouvez voir qu'on fait appel au serveur en indiquant quelle fonction (ping) et en lui passant le paramètre ($pay_hr). Ce dernier est une structure spécifique à Perl qui est le HASH (en gros un tableau associatif avec un tableau dans un tableau). Il faut recréer cette même structure en PHP. Pour cela on va créer un tableau associatif. Le header_hr vient en premier : $this->header_hr = array ( "cipux_version" => new xmlrpcval('3.4.0.0','string'), "client_name" => new xmlrpcval("cipux_cat_moodle","string"), "client_version" => new xmlrpcval("0.1","double"), "rpc_version" => new xmlrpcval("2.0","double"), "client_key" => new xmlrpcval("", "string"), "client_cred" => new xmlrpcval("", "string"), "gmt_time" => new xmlrpcval(time(), "i4") ); Vous pouvez constater que le header_hr de Perl et celui de PHP sont assez identiques. La différence vient du fait que toutes les valeurs de ce tableau doivent être des objets "xmlrpcval" sinon cela ne fonctionne pas. En plus, il faut spécifier le type de ces valeurs. Par défaut, si le type n'est pas précisé, la librairie xmlrpc.inc considère que la valeur est une chaîne de caractères (string). Ce header_hr ne changera jamais, il se trouve dans le constructeur de la classe cipux_xml. Il ne faut pas le modifier. Passons à la suite. Pour chaque fonction qui vont être créées pour cipux_xml, il faut créer un payload (pay_hr). Ce payload (regarder le code Perl plus haut) doit contenir le header_hr (cela revient à faire un tableau imbriqué ou tableau dans un tableau). public function ping () { $param_hr = array (); $pay_hr = array ( "header_hr" => new xmlrpcval($this->header_hr, "struct"), "login" => new xmlrpcval("dummy", "string"), // here the login of cipux "ticket" => new xmlrpcval("dummy", "string"), // here the ticket given at the first login "cmd" => new xmlrpcval("ping", "string"), // here the name of the command "param_hr" => new xmlrpcval($param_hr, "struct") ); $f = new xmlrpcmsg('ping', array(new xmlrpcval ($pay_hr, "struct"))); return $this->_standard_call($f); } Donc on intègre dans le pay_hr le header_hr en tant qu'objet xmlrpc (xmlrpcval) et le définir en tant que structure. Il y a un deuxième tableau dans cette fonction, qui est param_hr. Ce doit aussi être un tableau. Pour d'autre fonction CipUX (somme, creation...), ce tableau contiendra les valeurs à passer en paramètres à ces fonctions. Ici, PING n'a pas besoin de paramètres donc c'est un tableau vide qu'on insère en tant qu'objet XML-RPC (xmlrpcval) de type structure (struct). CipUX-RPC-3.4.0.9/usr000755001750001750 011432067074 14711 5ustar00ckuelkerckuelker000000000000CipUX-RPC-3.4.0.9/usr/share000755001750001750 011432067074 16013 5ustar00ckuelkerckuelker000000000000CipUX-RPC-3.4.0.9/usr/share/cipux000755001750001750 011432067074 17143 5ustar00ckuelkerckuelker000000000000CipUX-RPC-3.4.0.9/usr/share/cipux/etc000755001750001750 011432067074 17716 5ustar00ckuelkerckuelker000000000000CipUX-RPC-3.4.0.9/usr/share/cipux/etc/cipux-rpc.ini000444001750001750 704211432067074 22471 0ustar00ckuelkerckuelker000000000000# +=========================================================================+ # || /usr/share/cipux/etc/cipux-rpc.ini || # || || # || Configuration file for the CipUX XML-RPC server. || # || || # || Copyright (c) 2006 - 2008 by Christian Kuelker || # || || # || License: GNU GPL - GNU General Public License - version 2 || # || or (at your opinion) any later version. || # || || # +=========================================================================+ # This variable is in use, but have no effect. If you # want to change, please do it in /usr/sbin/cipux_rpc # sorry for the inconvinience! # # It is also in use for cipux_rpc_test_client and # can be overwritten with --port option # # Default: xml_rpc_port = 8001 xml_rpc_port = 8001 # Change is not suppored nor make it sense # Other then 'localhost' or '127.0.0.1' would also be # not a good idea in a secure environment. # # It is also in use for cipux_rpc_test_client and # can be overwritten with --host option. # # Default value: localhost # Alternative value: 127.0.0.1 xml_rpc_address = localhost # This defines if we use SSL or not. Unfortunately # we do only support non SSL sessions for now. We do # plan to support SSL in the future. For now please # secure the connection with stunnel or similar # wrapper. # # It is also in use for cipux_rpc_test_client and # can be overwritten with --ssl and --no-ssl option. # # Possible values: 0 # Default: 0 xml_rpc_ssl = 0 # Try to reuse the port when starting? # Remark: This can make problems on older software (Debian Sarge) # Possible values: 0|1 # Default: 1 xml_rpc_reuse = 1 # protocol to use # Default: tcp xml_rpc_proto = tcp # Live time of the ticket in seconds # # It is also in use for cipux_rpc_test_client # # Default: xml_rpc_ticket_ttl = 600 xml_rpc_ticket_ttl = 600 # Location of the PID file # Default: xml_rpc_pid_file = /var/run/cipux_rpcd.pid xml_rpc_pid_file = /var/run/cipux_rpcd.pid # reserved for future use: xml_rpc_use_pam = 1 # reserved for future use: # xml_rpc_rbac_cttl Cache time to live for RBAC lists in seconds: # # This value determines after how many seconds a new query to generate # the RBAC ACLs should be made. This is of importance when adding a new # user to the system or when adding or removing access to CAT modules. # It means you (and your users) should wait before expecting to see # changes in the system when you or your user made changes to the RBAC # layer. If you set this to low the server response time might be very # slow! Example: If you add a user to a group to have access to a given # CAT module, the user has to wait maximum "xml_rpc_rbac_cttl" seconds # before the user can access that module. (default 600 sec). Values # from 10 minutes (600 sec) up to 24 hours might be forseen. # # On startup the server will made a query anyway! # # Possible Values: # -1 : no query (after initial query) # 0 : a query every time # > 0: time in second the cache is valid # # Default: 600 xml_rpc_rbac_cttl = 600 # Specify a group which has access to the XML-RPC internal commands, like # reload XML-RPC grants trough flushing cache. # # Possible value: one group name. # # Default value: admin xml_rpc_intern_admin_group = admin CipUX-RPC-3.4.0.9/sbin000755001750001750 011432067074 15033 5ustar00ckuelkerckuelker000000000000CipUX-RPC-3.4.0.9/sbin/cipux_rpcd000444001750001750 714611432067074 17263 0ustar00ckuelkerckuelker000000000000#!/usr/bin/perl -w -T # +=======================================================================+ # || /usr/sbin/cipux_rpcd || # || || # || Daemon for the CipUX::Task API of CipUX || # || || # || Copyright (C) 2007 - 2009 by Christian Kuelker || # || All rights reserved! || # || || # || License: GNU General Public License - GPL version 2 || # || or (at your opinion) any later version. || # || || # +=======================================================================+ # ID: $Id$ # Revision: $Revision$ # Head URL: $HeadURL$ # Date: $Date$ # Source: $Source$ package cipux_rpcd; use 5.008001; use strict; use warnings; use CipUX::RPC::Server::Daemon; use version; our $VERSION = qv('3.4.0.0'); delete @ENV{qw(PATH IFS CDPATH ENV BASH_ENV)}; # Make %ENV safer # +=============================================================================+ # || MAIN || # +=============================================================================+ my $daemon = CipUX::RPC::Server::Daemon->new( { name => 'cipux_rpcd' } ); $daemon->run(); exit 0; __END__ #=========================================================================== #==== Documentation ======================================================== #=========================================================================== =pod =head1 NAME cipux_rpcd - CipUX XML-RPC server daemon =head1 VERSION version 3.4.0.0 =head1 SYNOPSIS cipux_rpcd =head1 DESCRIPTION This is the CipUX XML-RPC daemon for the CipUX::Task API. The daemon provides CipUX commands to RPC clients. =head1 OPTIONS I<-c> Same as --cfg I<--cfg> Specifies the configuration. I<-D> The same as --debug I I The same as --help I I The same as --nofork I Do not for the server. I The same as --version. I Prints the version. =head1 USAGE cipux_rpcd [OPTIONS] =head1 EXAMPLE cipux_rpcd cipux_rpcd --debug =head1 REQUIRED ARGUMENTS None. =head1 DIAGNOSTICS None. =head1 CONFIGURATION None. =head1 DEPENDENCIES CipUX::RPC::Server::Daemon version =head1 INCOMPATIBILITIES Not known. =head1 BUGS AND LIMITATIONS Supports https only via stunnel4. =head1 SEE ALSO See the CipUX webpage and the manual at L See the mailing list L =head1 AUTHOR Christian Kuelker Echristian.kuelker@cipworx.orgE =head1 LICENSE AND COPYRIGHT Copyright (C) 2007 - 2009 by Christian Kuelker This program 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, or (at your option) any later version. This program 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 this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA =cut CipUX-RPC-3.4.0.9/sbin/cipux_mkcertkey000444001750001750 2345211432067074 20347 0ustar00ckuelkerckuelker000000000000#!/usr/bin/perl -wT # +=======================================================================+ # || cipux_mkcertkey || # || || # || Create a TLS certificate for stunnel. To change the default || # || settings, edit /etc/cipux/cipux-cert.conf || # || || # || Copyright (C) 2008 by Christian Kuelker. All rights reserved. || # || || # || License: GNU General Public License - GNU GPL - version 2 || # || or (at your opinion) any later version. || # || || # +=======================================================================+ # ID: $Id$ # Revision: $Revision$ # Head URL: $HeadURL$ # Date: $Date$ # Source: $Source$ use strict; use warnings; use 5.008001; use re 'taint'; # Keep data captured by parens tainted use Carp; use CipUX; use File::stat; use Cwd; use POSIX qw(sysconf _PC_CHOWN_RESTRICTED); use Readonly; use Fatal qw(open close); use English qw( -no_match_vars); use version; our $VERSION = qv('3.4.0.0'); # +=======================================================================+ # || GLOBAL CONSTANTS || # +=======================================================================+ delete @ENV{qw(PATH IFS CDPATH ENV BASH_ENV)}; # Make %ENV safer Readonly::Scalar my $EMPTY_STRING => q{}; Readonly::Scalar my $SCRIPT => 'cipux_mkcertkey'; Readonly::Scalar my $OPENSSLBIN => '/usr/bin/openssl'; Readonly::Scalar my $CERTCONF => '/etc/cipux/stunnel-cert.conf'; Readonly::Scalar my $KEYDIR => '/etc/cipux/stunnel'; Readonly::Scalar my $KEY => $KEYDIR . '/stunnel-key.pem'; Readonly::Scalar my $CERT => $KEYDIR . '/stunnel-cert.pem'; Readonly::Scalar my $S4USER => 'stunnel4'; Readonly::Scalar my $SOME_ELSE_CAN_WRITE => oct 22; # 022 Readonly::Scalar my $STICKY_BIT => oct 1000; # 01000 # +=======================================================================+ # || GLOBAL VARS || # +=======================================================================+ my $debug = 0; # +=======================================================================+ # || TESTS || # +=======================================================================+ # test if we have some tiny configuration file if ( not -e $CERTCONF ) { croak 'Cannot find certificate configuration: ' . $CERTCONF . "\n"; } # test if we have some tool for creating the cert if ( not -x $OPENSSLBIN ) { croak 'Cannot find openssl executable: ' . $OPENSSLBIN . "\n"; } # test if there is a sneaky dir for storing certs if ( not -d $KEYDIR ) { croak 'Directory to store certs do not exist: ' . $KEYDIR . "\n"; } # test if KEY dir is save: mode 700, owner root if ( not is_verysafe($KEYDIR) ) { my $msg = 'Directory to store certs is not save!'; $msg .= ' Should be for example: drwx------ 2 root root'; $msg .= ' 4096 2008-04-17 21:15 /etc/cipux/stunnel '; croak $msg . "\n"; } # +=======================================================================+ # || GENERATE CERT || # +=======================================================================+ # +=======================================================================+ # || || # +=======================================================================+ # openssl req -new -x509 -nodes -days 365 # -out stunnel.pem -keyout stunnel.pem my $cmd = $OPENSSLBIN . ' req -new -x509 -nodes '; $cmd .= ' -config ' . $CERTCONF . ' -out ' . $CERT; $cmd .= ' -keyout ' . $KEY; open my $cert, q{|-}, $cmd or croak 'Cannot execute ' . $cmd . "\n"; my @out = <$cert>; close $cert or croak "Can not close $cmd \n"; # print out errors or other stuff in clean untainted manner: my $C = CipUX->new( { debug => $debug } ); foreach my $line (@out) { chomp $line; $C->l($line); print $line . "\n" or croak 'Can not print to STDOUT!'; } # change owner my ( $login, $pass, $uid, $gid ) = getpwnam $S4USER or croak "$S4USER not known to the system!"; chown $uid, $gid, $KEY; chown $uid, $gid, $CERT; # change file mode my $mode = '0600'; chmod oct($mode), $KEY; chmod oct($mode), $CERT; # change owner KYEDIR chown $uid, $gid, $KEYDIR; exit 0; sub is_safe { my $path = shift; my $info = stat $path; return if not $info; # owner neither superuser nor me # the real uid is in stored in the $< variable or ($REAL_USER_ID) if ( ( $info->uid != 0 ) && ( $info->uid != $REAL_USER_ID ) ) { return 0; } # check whether group or other can write file. # use 066 to detect either reading or writing if ( $info->mode & $SOME_ELSE_CAN_WRITE ) { # someone else can write this return 0 if not -d _; # non-directories aren't safe # but directories with the sticky bit (01000) are return 0 if not( $info->mode & $STICKY_BIT ); } return 1; } sub is_verysafe { my $path = shift; return is_safe($path) if sysconf(_PC_CHOWN_RESTRICTED); if ( $path !~ m{^/}xms ) { $path = getcwd() . q{/} . $path; } while ( length $path ) { return if not is_safe($path); $path =~ s{([^/]+|/)$}{}xms; # dirname if ( length($path) > 1 ) { $path =~ s{/$}{}xms; } } return 1; } __END__ #=========================================================================== #==== Documentation ======================================================== #=========================================================================== =pod =head1 NAME cipux_mkcertkey - simple script to generate certificate for stunnel =head1 VERSION version 3.4.0.0 =head1 SYNOPSIS cipux_mkcertkey =head1 REQUIRED ARGUMENTS None. =head1 ABSTRACT In order to add security to your XML-RPC server you should generate a certificate. This script shows a simple method to do that. You have to take the responsibility by yourself to make sure you understand what you do. =head1 DESCRIPTION Generates a certificate and a key in /etc/cipux/stunnel. =head1 USAGE cipux_mkcertkey =head1 OPTIONS None. =head1 CERTIFICATE Each SSL enabled XML-RPC server needs to present a valid X.509 certificate to the peer and it also needs a private key to decrypt the incoming data. The easiest way to obtain a certificate and a key is to generate them with the free openssl package. You can find more information on certificates generation below. The certificates must be in PEM format and must be sorted starting with the certificate to the highest level (root CA) Two things are important when generating the certificate-key pairs. (1) Because the server has no way to obtain the password from the user, the private key cannot be encrypted. To create an unencrypted key add the "-nodes" option when running the req command from the openssl kit. (2) The order of contents of the .pem file is also important. It should contain the unencrypted private key first, then a signed certificate (not certificate request). There should be also empty lines after certificate and private key. Plaintext certificate information appended on the top of generated certificate should be discarded. So the file should look like this: -----BEGIN RSA PRIVATE KEY----- [encoded key] -----END RSA PRIVATE KEY----- [empty line] -----BEGIN CERTIFICATE----- [encoded certificate] -----END CERTIFICATE----- [empty line] This can be stored in one file or in two files. This script stores the in to files to have the flexibility to use the certificate in other location. This to files will be created: stunnel-cert.pem stunnel-key.pem =head1 DIAGNOSTICS TODO: write explanations to the messages. =over =item C<< Cannot find certificate configuration: %s >> =item C<< Cannot find openssl executable: %s >> =item C<< Directory to store certs do not exist: %s >> =item C<< Directory to store certs is not save!... >> Directory to store certs is not save! Should be for example: drwx------ 2 root root 4096 2008-04-17 21:15 /etc/cipux/stunnel =item C<< Cannot execute %s >> =item C<< Can not close %s >> =item C<< Can not print to STDOUT! >> =item C<< %s not known to the system! >> =back =head1 CONFIGURATION TODO. =head1 DEPENDENCIES Carp CipUX File::stat Cwd POSIX Readonly Fatal English version =head1 INCOMPATIBILITIES Not known. =head1 BUGS AND LIMITATIONS Not known. =head1 SEE ALSO See the CipUX webpage and the manual at L See the mailing list L =head1 AUTHOR Christian Kuelker Echristian.kuelker@cipworx.orgE =head1 LICENSE AND COPYRIGHT Copyright (C) 2008 by Christian Kuelker This program 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, or (at your option) any later version. This program 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 this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA =cut CipUX-RPC-3.4.0.9/sbin/cipux_rpcdr000444001750001750 1134011432067074 17454 0ustar00ckuelkerckuelker000000000000#!/usr/bin/perl -w -T # +=======================================================================+ # || /usr/sbin/cipux_rpcdr || # || || # || Start the CipUX RPC remote daemon || # || || # || (C) Copyright 2007 by Christian Kuelker || # || (C) Copyright 2007 by Sebastian Friedel || # || (C) Copyright 2008 - 2009 by Christian Kuelker || # || All rights reserved! || # || || # || License: GNU General Public License - GNU GPL - version 2 || # || or (at your opinion) any later version. || # || || # +=======================================================================+ # ID: $Id$ # Revision: $Revision$ # Head URL: $HeadURL$ # Date: $Date$ # Source: $Source$ package cipux_rpcdr; use strict; use warnings; use Carp; use CipUX; use Fatal qw(open close); use English qw( -no_match_vars); use Readonly; use version; our $VERSION = qv('3.4.0.0'); # +=========================================================================+ # || CONSTANTS || # +=========================================================================+ delete @ENV{qw(PATH IFS CDPATH ENV BASH_ENV)}; # Make %ENV safer Readonly::Scalar my $EMPTY_STRING => q{}; Readonly::Scalar my $SCRIPT => 'cipux_rpcdr'; local $PROGRAM_NAME = "$SCRIPT [accepting connections]"; # +=========================================================================+ # || GLOBAL VARS || # +=========================================================================+ my $stunnel = $EMPTY_STRING; my $pid = 0; # we need to know the location of stunnel, because we use this path # as input for pidof. so we can not use PATH for pidof. if ( -x '/usr/bin/stunnel4' ) { $stunnel = '/usr/bin/stunnel4'; } elsif ( -x '/usr/sbin/stunnel4' ) { $stunnel = '/usr/sbin/stunnel4'; } else { croak "stunnel4 was not found in /usr/bin or /usr/sbin!\n"; } open my $stdin, q{|-}, "/bin/pidof -s $stunnel " or croak "Can not open $stunnel\n"; my @pid = <$stdin>; close $stdin or croak 'Can not close access to pidof!'; # untaint data my $cipux = CipUX->new( { debug => 0 } ); foreach my $p (@pid) { $cipux->l($p); } if ( defined $pid[0] ) { $pid = $pid[0]; } chomp $pid; if ( $pid == 0 ) { system "$stunnel /etc/cipux/stunnel.conf"; } else { die "stunnel (alias cipux_rpcdr) is already running at $pid!\n"; } exit 0; __END__ #=========================================================================== #==== Documentation ======================================================== #=========================================================================== =pod =head1 NAME cipux_rpcdr - Remote CipUX XML-RPC Sever wrapper for Stunnel =head1 VERSION version 3.4.0.0 =head1 SYNOPSIS cipux_rpcdr =head1 DESCRIPTION This is the CipUX XML-RPC remote daemon for the CipUX::Task API. The daemon provides CipUX commands to RPC clients over SSL. Start the cipux_rpcdr server after the cipux_rpcd server. =head1 USAGE /usr/sbin/cipux_rpcdr =head1 REQUIRED ARGUMENTS None. =head1 OPTIONS TODO =head1 DIAGNOSTICS TODO =head1 CONFIGURATION Need stunnel configuration. TODO =head1 DEPENDENCIES Carp CipUX English Fatal Readonly version =head1 INCOMPATIBILITIES Not known. =head1 BUGS AND LIMITATIONS Not known. =head1 SEE ALSO See the CipUX webpage and the manual at L See the mailing list L =head1 AUTHOR Christian Kuelker Echristian.kuelker@cipworx.orgE =head1 LICENSE AND COPYRIGHT Copyright (C) 2007 - 2009 by Christian Kuelker This program 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, or (at your option) any later version. This program 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 this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA =cut CipUX-RPC-3.4.0.9/sbin/cipux_rpc_test_repetition000444001750001750 2217711432067074 22441 0ustar00ckuelkerckuelker000000000000#!/usr/bin/perl -w -T # +=======================================================================+ # || cipux_rpc_test_repetition || # || || # || Repeats a single RPC test for an amount of time. || # || || # || Copyright (C) 2009 by Christian Kuelker. All rights reserved! || # || || # || License: GNU GPL version 2 or any later version || # || || # +=======================================================================+ # ID: $Id$ # Revision: $Revision$ # Head URL: $HeadURL$ # Date: $Date$ # Source: $Source$ package cipux_rpc_test_repetition; use 5.008001; use strict; use warnings; use Carp; use CipUX::RPC; use Data::Dumper; use Date::Manip; use English qw( -no_match_vars); use Frontier::Client; use Getopt::Long; use Log::Log4perl qw(get_logger :levels); use Pod::Usage; use Readonly; use version; our $VERSION = qv('3.4.0.0'); delete @ENV{qw(PATH IFS CDPATH ENV BASH_ENV)}; # Make %ENV safer # +==========================================================================+ # || CONST || # +==========================================================================+ Readonly::Scalar my $EMPTY_STRING => q{}; Readonly::Scalar my $L4P_CONF => ( -e '/etc/cipux/log4perl.conf' ) ? '/etc/cipux/log4perl.conf' : $EMPTY_STRING; # +==========================================================================+ # || GLOBAL || # +==========================================================================+ my $login = 'dummy'; my $password = undef; my $ticket = 'dummy'; my $host = 'localhost'; my $port = 8001; my $proto = 'http'; my $url = $proto . q{://} . $host . q{:} . $port . q{/RPC2}; my %opt = (); my $starttime = UnixDate( 'today', '%O' ); my $startsec = time(); my $stopsec = $startsec; # +==========================================================================+ # || ENV || # +==========================================================================+ Getopt::Long::Configure('bundling'); GetOptions( \%opt, 'debug|D', 'help|h|?', 'host=s', 'time=i', 'port=i', 'no-ssl', 'ssl', 'version|V', ) or pod2usage( -exitstatus => 2, -msg => "Problems parsing command line!\n" ); if ( exists $opt{help} ) { pod2usage( -exitstatus => 0, -verbose => 0 ); } if ( exists $opt{version} ) { print "$0 $VERSION\n"; exit 0; } if ( exists $opt{debug} and defined $opt{debug} ) { Log::Log4perl::init_once($L4P_CONF); } if ( not exists $opt{time} and not defined $opt{time} ) { pod2usage( -exitstatus => 32, -msg => "Mandatory option --time is missing!\n" ); } else { $stopsec += $opt{time}; } my $logger = get_logger(__PACKAGE__); $logger->debug('BEGIN'); $logger->debug("url $url"); $logger->debug("L4P_CONF $L4P_CONF"); # +==========================================================================+ # || MAIN || # +==========================================================================+ my $rpc = CipUX::RPC->new(); $login = $rpc->login_prompt( { prompt => 'Login: ' } ); $password = $rpc->password_prompt( { prompt => 'Password: ' } ); my $answer_hr = xmlrpc( { type => 'login', cmd => 'login', param_hr => { password => $password, } } ); print "login status: $answer_hr->{status}\n"; print "start at: $startsec\n"; print "stop at: $stopsec\n"; my $sec = $startsec; my $true = 0; my $false = 0; my $cmd = 'cipux_task_list_user_accounts'; my $param_hr = {}; while ( $sec < $stopsec ) { print '=' x 76, "\n"; $answer_hr = xmlrpc( { type => 'task', cmd => $cmd, param_hr => $param_hr } ); print "$cmd at $sec status $answer_hr->{status} ($ticket)\n"; $sec = time(); if ( $answer_hr->{status} eq 'TRUE' ) { $true++; } else { $false++; print Dumper($answer_hr); exit 1; } # simple renew session always $answer_hr = xmlrpc( { type => 'session', cmd => 'session', param_hr => {} } ); } print "Summary:\n"; print "start at: $startsec\n"; print "stop at: $stopsec\n"; print "seconds: $opt{time}\n"; print "true : $true\n"; print "false : $false\n"; exit 0; sub xmlrpc { my ($arg_r) = @_; my $type = $arg_r->{type}; my $cmd = $arg_r->{cmd}; my $param_hr = $arg_r->{param_hr}; my $logger = get_logger(__PACKAGE__); $logger->debug("type: $type"); $logger->debug("cmd: $cmd"); my $HEADER_HREF = { 'cipux_version' => '3.4.0.0', 'client_name' => 'cipux_rpc_test_client', 'client_version' => '3.4.0.0', 'rpc_version' => '2.0', 'client_key' => $EMPTY_STRING, 'client_cred' => $EMPTY_STRING, 'gmt_time' => time(), }; print "pay ticket $ticket\n"; my $pay_hr = { header_hr => $HEADER_HREF, login => $login, ticket => $ticket, cmd => $cmd, param_hr => $param_hr }; my $socket = Frontier::Client->new( url => $url ); my $answer_hr = $socket->call( $type, $pay_hr ); $logger->debug( 'answer_hr: ', { filter => \&Dumper, value => $answer_hr } ); my $status = $answer_hr->{status} || 'UNKNOWN'; # remember, if we got new ticket on the default channel if ( defined $answer_hr->{ticket} and $answer_hr->{ticket} ne $EMPTY_STRING ) { $ticket = $answer_hr->{ticket}; print "default channel ticket $ticket\n"; } # but if we got a ticket explcit: eq. login or session # we should use this of course if ( ref( $answer_hr->{cmdres_r} ) eq 'HASH' and defined $answer_hr->{cmdres_r}->{ticket} and $answer_hr->{cmdres_r}->{ticket} ne $EMPTY_STRING ) { $ticket = $answer_hr->{cmdres_r}->{ticket}; print "explicit channel ticket $ticket\n"; } return $answer_hr; } __END__ =pod =head1 NAME cipux_rpc_test_repetition =head1 VERSION version 3.4.0.0 =head1 SYNOPSIS cipux_rpc_test_repetition [OPTIONS] --time cipux_rpc_test_repetition --help|-h cipux_rpc_test_repetition --version|-V Options: --debug : prints debug messages to cipux-test.log --host : rpc server address, examples: localhost, 127.0.0.1, ldap --no-ssl : rpc server use no SSL --port : rpc server port, example: 8001 --ssl : rpc server use SSL =head1 DESCRIPTION This is a test script for users admins or packages to test if the cipux-RPC server is working. =head1 REQUIRED ARGUMENTS None. =head1 OPTIONS =over 4 I<--debug> Prints debug messages to according /etc/cipux/log4perl.conf I<-h> See --help I<--help> Prints short usage message. I<--host NAME> Address of the RPC server. For example localhost, 127.0.0.1, ldap or other names. I<--no-ssl> Access CipUX XML-RPC server only over http, not https. I<--port NUMBER> Port number of the XML RPC server. For example 8001. I<--ssl> Access CipUX XML-RPC server over SSL aka https. I<--time SECONDS> Executes test up to this amount of time. =back =head1 DIAGNOSTICS =over =item C<< Problems parsing command line! >> If Getopt::Long has a non specified problem. See Getopt::Long for details. Most probable reason is: you provide wrong command line switches or values. =item C<< Mandatory option --time is missing! >> The CLI option --time was not given. Give the option '--time ' where you replace the with an integer number of seconds > 0. This is the time you would like to run the test in seconds. =back =head1 EXIT STATUS 1 on failure 0 on success other from XML-RPC server =head1 CONFIGURATION Not needed. =head1 DEPENDENCIES Carp CipUX::RPC Data::Dumper Date::Manip English Frontier::Client Getopt::Long Log::Log4perl Pod::Usage Readonly =head1 INCOMPATIBILITIES Not known. =head1 BUGS AND LIMITATIONS Not known. =head1 AUTHOR Christian Kuelker Echristian.kuelker@cipworx.orgE =head1 LICENSE AND COPYRIGHT Copyright (C) 2009 by Christian Kuelker This program 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, or (at your option) any later version. This program 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 this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA =cut CipUX-RPC-3.4.0.9/sbin/cipux_rpc_test_client000444001750001750 763011432067074 21512 0ustar00ckuelkerckuelker000000000000#!/usr/bin/perl -w -T # +=======================================================================+ # || cipux_rpc_test_client || # || || # || Testclient for CipUX XML-RPC Server || # || || # || Copyright (C) 2007 - 2008 by Christian Kuelker. || # || || # || License: GNU GPL - GNU General Public License - version 2 || # || or (at your opinion) any later version. || # || || # +=======================================================================+ # ID: $Id$ # Revision: $Revision$ # Head URL: $HeadURL$ # Date: $Date$ # Source: $Source$ package cipux_rpc_test_client; use 5.008001; use strict; use warnings; use CipUX::RPC::Test::Client; use version; our $VERSION = qv('3.4.0.0'); delete @ENV{qw(PATH IFS CDPATH ENV BASH_ENV)}; # Make %ENV safer # +=============================================================================+ # || MAIN || # +=============================================================================+ my $client = CipUX::RPC::Test::Client->new( { name => 'cipux_rpc_test_client' } ); $client->run(); exit 0; __END__ =pod =head1 NAME cipux_rpc_test_client - command line tool to test the RPC server =head1 VERSION version 3.4.0.0 =head1 SYNOPSIS cipux_rpc_test_client [OPTIONS] cipux_rpc_test_client --help|-h cipux_rpc_test_client --version|-V Options: --debug : prints debug messages to cipux-test.log --host : rpc server address, examples: localhost, 127.0.0.1, ldap --maxtest : executes until and including max. number of tests --no-ssl : rpc server use no SSL --port : rpc server port, example: 8001 --ssl : rpc server use SSL =head1 DESCRIPTION This is a test script for users admins or packages to test if the cipux-RPC server is working. =head1 REQUIRED ARGUMENTS None. =head1 OPTIONS =over 4 I<--debug> Prints debug messages to cipux-test.log. I<-h> See --help I<--help> Prints short usage message. I<--host NAME> Address of the RPC server. For example localhost, 127.0.0.1, ldap or other names. I<--maxtest NUMBER_OF_TESTS> Runs up to NUMBER_OF_TESTS tests. The script can not stop after every test, but after the next possible test. Useful for debugging. I<--no-ssl> Access CipUX XML-RPC server only over http, not https. I<--port NUMBER> Port number of the XML RPC server. For example 8001. I<--ssl> Access CipUX XML-RPC server over SSL aka https. =back =head1 DIAGNOSTICS None. =head1 EXIT STATUS 1 on failure 0 on success other from XML-RPC server =head1 CONFIGURATION not needet. =head1 DEPENDENCIES CipUX::RPC::Test::Client version =head1 INCOMPATIBILITIES Not known. =head1 BUGS AND LIMITATIONS Not known. =head1 AUTHOR Christian Kuelker Echristian.kuelker@cipworx.orgE =head1 LICENSE AND COPYRIGHT Copyright (C) 2007 - 2008 by Christian Kuelker This program 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, or (at your option) any later version. This program 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 this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA =cut CipUX-RPC-3.4.0.9/etc000755001750001750 011432067074 14653 5ustar00ckuelkerckuelker000000000000CipUX-RPC-3.4.0.9/etc/init.d000755001750001750 011432067074 16040 5ustar00ckuelkerckuelker000000000000CipUX-RPC-3.4.0.9/etc/init.d/cipux-rpcd000444001750001750 660011432067074 20200 0ustar00ckuelkerckuelker000000000000#! /bin/sh ### BEGIN INIT INFO # Provides: cipux-rpcd # Required-Start: $remote_fs $syslog # Required-Stop: $remote_fs $syslog # Should-Start: slapd # Default-Start: 2 3 4 5 # Default-Stop: 1 # Short-Description: XML-RPC server for CipUX tasks # Description: This server provides an XML-RPC API for the CipUX # task library CipUX::Task. You can plug in external # applications to execute generic CipUX commands. # This server should actually be accessed via socket # cipux_rpcdr stunnel4 wrapper. ### END INIT INFO # Author: Jonas Smedegaard PATH=/sbin:/usr/sbin:/bin:/usr/bin DESC="CipUX XML-RPC daemon" NAME=cipux-rpcd DAEMON=/usr/sbin/cipux_rpcd DAEMON_ARGS="" PIDFILE="/var/run/$NAME.pid" SCRIPTNAME="/etc/init.d/$NAME" NOSTARTFILE="/etc/cipux/${NAME}_not_to_be_run" [ -x "$DAEMON" ] || exit 0 [ -r "/etc/default/$NAME" ] && . "/etc/default/$NAME" . /lib/init/vars.sh . /lib/lsb/init-functions DAEMONBASENAME="$(basename "$DAEMON")" check_for_no_start() { [ ! -e "$NOSTARTFILE" ] || return 1 } check_config() { $DAEMON -t > /dev/null || return 1 } do_start() { start-stop-daemon --background --make-pidfile --start --quiet --pidfile "$PIDFILE" --startas "$DAEMON" --name "$DAEMONBASENAME" --test > /dev/null \ || return 1 # TODO: drop --background and --make-pidfile when implemented in daemon itself start-stop-daemon --background --make-pidfile --start --quiet --pidfile "$PIDFILE" --startas "$DAEMON" --name "$DAEMONBASENAME" -- \ $DAEMON_ARGS \ || return 2 } do_stop() { start-stop-daemon --stop --quiet --retry=TERM/30/KILL/5 --pidfile "$PIDFILE" --name "$DAEMONBASENAME" RETVAL="$?" [ "$RETVAL" = 2 ] && return 2 rm -f "$PIDFILE" return "$RETVAL" } do_reload() { start-stop-daemon --stop --signal 1 --quiet --pidfile "$PIDFILE" --name "$DAEMONBASENAME" return 0 } case "$1" in start) [ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC" "$NAME" check_for_no_start case "$?" in 0) do_start case "$?" in 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;; 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;; esac ;; *) [ "$VERBOSE" != no ] && log_warning_msg "$DESC disabled ($NOSTARTFILE found)" [ "$VERBOSE" != no ] && log_end_msg 0 ;; esac ;; stop) [ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME" do_stop case "$?" in 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;; 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;; esac ;; reload|force-reload) log_daemon_msg "Reloading $DESC" "$NAME" check_for_no_start case "$?" in 0) check_config case "$?" in 0) do_reload log_end_msg $? ;; *) log_warning_msg "configuration test failed for $NAME" log_end_msg 1 ;; esac ;; *) log_warning_msg "$DESC disabled ($NOSTARTFILE found)" log_end_msg 0 ;; esac ;; restart) log_daemon_msg "Restarting $DESC" "$NAME" do_stop case "$?" in 0|1) check_for_no_start case "$?" in 0) do_start case "$?" in 0) log_end_msg 0 ;; 1) log_end_msg 1 ;; # Old process is still running *) log_end_msg 1 ;; # Failed to start esac ;; *) log_warning_msg "$DESC stopped only ($NOSTARTFILE found)" log_end_msg 0 ;; esac ;; *) # Failed to stop log_end_msg 1 ;; esac ;; *) echo "Usage: $SCRIPTNAME {start|stop|restart|reload|force-reload}" >&2 exit 3 ;; esac : CipUX-RPC-3.4.0.9/etc/init.d/cipux-rpcdr000444001750001750 771311432067074 20370 0ustar00ckuelkerckuelker000000000000#! /bin/sh ### BEGIN INIT INFO # Provides: cipux-rpcdr # Required-Start: $remote_fs $syslog # Required-Stop: $remote_fs $syslog # Default-Start: 2 3 4 5 # Default-Stop: 0 1 6 # Short-Description: SSL wrapper for XML-RPC server for CipUX. # Description: This stunnel4 start script provides an SSL wrapper for # the XML-RPC server for CipUX. You can plug in external # applications to execute generic CipUX commands. You # should use this SSL wrapper to access the XML-RPC server # over a network connection. ### END INIT INFO # Author: Jonas Smedegaard PATH=/sbin:/usr/sbin:/bin:/usr/bin DESC="CipUX XML-RPC daemon" NAME=cipux-rpcdr DAEMON=/usr/sbin/cipux_rpcdr DAEMON_ARGS="" PIDFILE="/var/run/$NAME.pid" SCRIPTNAME="/etc/init.d/$NAME" NOSTARTFILE="/etc/cipux/${NAME}_not_to_be_run" STUNNEL=/usr/bin/stunnel4 STUNNELFILE=/etc/cipux/stunnel-cert.conf [ -x "$DAEMON" ] || exit 0 [ -r "/etc/default/$NAME" ] && . "/etc/default/$NAME" . /lib/init/vars.sh . /lib/lsb/init-functions if [ ! -x "$DAEMON" ]; then log_error_msg "Could not find Binary $STUNNEL" exit 5 fi check_for_no_start() { [ ! -e "$NOSTARTFILE" ] || return 1 } check_config() { $DAEMON -t > /dev/null || return 1 } do_start() { start-stop-daemon --start --quiet --pidfile "$PIDFILE" --exec "$DAEMON" --test > /dev/null \ || return 1 # FIXME: is --background and --make-pidfile needed? (not in skeleton) start-stop-daemon --background --make-pidfile --start --quiet --pidfile "$PIDFILE" --exec "$DAEMON" -- \ $DAEMON_ARGS \ || return 2 # FIXME: is sleep and pidfile update needed (not in skeleton)? sleep 1 pid="$(pidof "$STUNNEL" "$STUNNELFILE")" if [ -z "$pid" ]; then echo "" else if [ "$pid" -ne "$(cat "$PIDFILE")" ]; then echo "$pid" > "$PIDFILE" fi fi } do_stop() { # FIXME: Do we not trust existing pidfile?!? pid="$(pidof "$STUNNEL" "$STUNNELFILE")" if [ "$pid" -ne "$(cat "$PIDFILE")" ]; then echo "$pid" > "$PIDFILE" fi start-stop-daemon --stop --quiet --retry=TERM/30/KILL/5 --pidfile "$PIDFILE" RETVAL="$?" [ "$RETVAL" = 2 ] && return 2 sleep 5 # FIXME: is pidfile removal needed (suggested in skeleton)? #rm -f "$PIDFILE" return "$RETVAL" } do_reload() { start-stop-daemon --stop --signal 1 --quiet --pidfile "$PIDFILE" --exec "$DAEMON" # FIXME: is sleep needed (not in skeleton)? sleep 5 return 0 } case "$1" in start) [ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC" "$NAME" check_for_no_start case "$?" in 0) do_start case "$?" in 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;; 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;; esac ;; *) [ "$VERBOSE" != no ] && log_warning_msg "$DESC disabled ($NOSTARTFILE found)" [ "$VERBOSE" != no ] && log_end_msg 0 ;; esac ;; stop) [ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME" do_stop case "$?" in 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;; 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;; esac ;; reload|force-reload) log_daemon_msg "Reloading $DESC" "$NAME" check_for_no_start case "$?" in 0) check_config case "$?" in 0) do_reload log_end_msg $? ;; *) log_warning_msg "configuration test failed for $NAME" log_end_msg 1 ;; esac ;; *) log_warning_msg "$DESC disabled ($NOSTARTFILE found)" log_end_msg 0 ;; esac ;; restart) log_daemon_msg "Restarting $DESC" "$NAME" do_stop case "$?" in 0|1) check_for_no_start case "$?" in 0) do_start case "$?" in 0) log_end_msg 0 ;; 1) log_end_msg 1 ;; # Old process is still running *) log_end_msg 1 ;; # Failed to start esac ;; *) log_warning_msg "$DESC stopped only ($NOSTARTFILE found)" log_end_msg 0 ;; esac ;; *) # Failed to stop log_end_msg 1 ;; esac ;; *) echo "Usage: $SCRIPTNAME {start|stop|restart|reload|force-reload}" >&2 exit 3 ;; esac : CipUX-RPC-3.4.0.9/etc/cipux000755001750001750 011432067074 16003 5ustar00ckuelkerckuelker000000000000CipUX-RPC-3.4.0.9/etc/cipux/stunnel.conf000444001750001750 346611432067074 20510 0ustar00ckuelkerckuelker000000000000; Sample stunnel configuration file by Michal Trojnara 2002-2005 ; Some options used here may not be adequate for your particular configuration ; Please make sure you understand them (especially the effect of chroot jail) ; Certificate/key is needed in server mode and optional in client mode ;cert = /usr/etc/stunnel/mail.pem ;key = /usr/etc/stunnel/mail.pem ; ckuelker 2006-07-01 add cert location cert = /etc/cipux/stunnel/stunnel-cert.pem key = /etc/cipux/stunnel/stunnel-key.pem ; Some security enhancements for UNIX systems - comment them out on Win32 ;chroot = /usr/var/stunnel/ setuid = stunnel4 setgid = stunnel4 ; PID is created inside chroot jail pid = /var/run/stunnel4/stunnel.pid ; Some performance tunings socket = l:TCP_NODELAY=1 socket = r:TCP_NODELAY=1 ;compression = rle ; Workaround for Eudora bug ;options = DONT_INSERT_EMPTY_FRAGMENTS ; Authentication stuff ;verify = 2 ; Don't forget to c_rehash CApath; CApath is located inside chroot jail: ;CApath = /certs ; It's often easier to use CAfile: ;CAfile = /usr/etc/stunnel/certs.pem ; Don't forget to c_rehash CRLpath; CRLpath is located inside chroot jail: ;CRLpath = /crls ; Alternatively you can use CRLfile: ;CRLfile = /usr/etc/stunnel/crls.pem ; Some debugging stuff useful for troubleshooting ; ckuelker 2006-07-01 turn of debug ;;debug =7 debug = 0 output = /var/log/stunnel4/stunnel.log ; Use it for client mode ; ckuelker 2006-07-01, comment it out, use it for server ;;client = yes ; Service-level configuration ; ckuelker 2006-07-01 comment out pop3s, imaps, ssmtp ;;[pop3s] ;;accept = 995 ;;connect = 110 ;;[imaps] ;;accept = 993 ;;connect = 143 ;;[ssmtp] ;;accept = 465 ;;connect = 25 ;[https] ;accept = 443 ;connect = 80 ;TIMEOUTclose = 0 ; vim:ft=dosini ; ckuelker 2006-07-01, add cipux_rpcdr [cipux_rpcdr] accept = 8000 connect = 8001 CipUX-RPC-3.4.0.9/etc/cipux/stunnel-cert.conf000444001750001750 46211432067074 21414 0ustar00ckuelkerckuelker000000000000RANDOM=/dev/random [ req ] default_bits = 1024 encrypt_key = yes distinguished_name = req_dn x509_extensions = cert_type prompt = no [ req_dn ] C=EU ST=NA L=CipUX O=XML-RPC server OU=Automatically-generated stunnel SSL key CN=stunnel emailAddress=postmaster@localhost [ cert_type ] nsCertType = server CipUX-RPC-3.4.0.9/etc/cipux/stunnel000755001750001750 011432067074 17473 5ustar00ckuelkerckuelker000000000000CipUX-RPC-3.4.0.9/etc/cipux/stunnel/readme.txt000444001750001750 27711432067074 21614 0ustar00ckuelkerckuelker000000000000 /etc/cipux/stunnel/README ========================= This directory is used for storing key and certificate for the cipux_rpcdr aka stunnel4 server. Please see cipux_mkcertkey for details. CipUX-RPC-3.4.0.9/t000755001750001750 011432067074 14343 5ustar00ckuelkerckuelker000000000000CipUX-RPC-3.4.0.9/t/perlcriticrc000444001750001750 101611432067074 17106 0ustar00ckuelkerckuelker000000000000# CipUX Perl::Critic Configuration # # SEVERITY NAME ...is equivalent to... SEVERITY NUMBER # ---------------------------------------------------- # gentle 5 # stern 4 # harsh 3 # cruel 2 # brutal 1 severity = harsh verbose = 11 CipUX-RPC-3.4.0.9/t/pod.t000444001750001750 21411432067074 15424 0ustar00ckuelkerckuelker000000000000#!perl -T use Test::More; eval "use Test::Pod 1.14"; plan skip_all => "Test::Pod 1.14 required for testing POD" if $@; all_pod_files_ok(); CipUX-RPC-3.4.0.9/t/perlcritic_cpan.t000444001750001750 101011432067074 20016 0ustar00ckuelkerckuelker000000000000#!perl # use strict; use warnings; use File::Spec; use Test::More; use English qw(-no_match_vars); if ( not $ENV{TEST_AUTHOR} ) { my $msg = 'Author test. Set $ENV{TEST_AUTHOR} to a true value to run.'; plan( skip_all => $msg ); } eval { require Test::Perl::Critic; }; if ($EVAL_ERROR) { my $msg = 'Test::Perl::Critic required to criticise code'; plan( skip_all => $msg ); } my $rcfile = File::Spec->catfile( 't', 'perlcriticrc' ); Test::Perl::Critic->import( -profile => $rcfile ); all_critic_ok(); CipUX-RPC-3.4.0.9/t/leaktrace.t000444001750001750 160011432067074 16615 0ustar00ckuelkerckuelker000000000000use Test::More tests => 4; use Test::LeakTrace; #diag('test CipUX::RPC'); leaks_cmp_ok { use CipUX::RPC; my $rpc = CipUX::RPC->new( { cache_dir => 'blib/cache' } ); } '<', 52; #diag('test CipUX::RPC::Server'); leaks_cmp_ok { use CipUX::RPC::Server; my $rpc = CipUX::RPC::Server->new( { cache_dir => 'blib/cache' } ); } '<', 5; #'memory leaks CipUX::RPC::Server'; #diag('test CipUX::RPC::Server::Daemon'); leaks_cmp_ok { use CipUX::RPC::Server::Daemon; my $rpc = CipUX::RPC::Server::Daemon->new( { name => 'cipux_rpcd', cache_dir => 'blib/cache' } ); } '<', 5; #'memory leaks CipUX::RPC::Server::Daemon'; #diag('test CipUX::RPC::Test::Client'); no_leaks_ok { use CipUX::RPC::Test::Client; my $rpc = CipUX::RPC::Test::Client->new( { name => 'cipux_rpc_test_client', cache_dir => 'blib/cache' } ); } 'no memory leaks CipUX::RPC::Test::Client'; CipUX-RPC-3.4.0.9/t/00.load.t000444001750001750 34611432067074 16005 0ustar00ckuelkerckuelker000000000000use Test::More tests => 4; BEGIN { use_ok('CipUX::RPC'); use_ok('CipUX::RPC::Server'); use_ok('CipUX::RPC::Server::Daemon'); use_ok('CipUX::RPC::Test::Client'); } diag("Testing CipUX::RPC $CipUX::RPC::VERSION"); CipUX-RPC-3.4.0.9/t/pod-coverage.t000444001750001750 25411432067074 17221 0ustar00ckuelkerckuelker000000000000#!perl -T use Test::More; eval "use Test::Pod::Coverage 1.04"; plan skip_all => "Test::Pod::Coverage 1.04 required for testing POD coverage" if $@; all_pod_coverage_ok(); CipUX-RPC-3.4.0.9/t/refcount.t000444001750001750 227411432067074 16517 0ustar00ckuelkerckuelker000000000000use Test::More tests => 8; use Test::Refcount; use CipUX::RPC; use CipUX::RPC::Server; use CipUX::RPC::Server::Daemon; use CipUX::RPC::Test::Client; #diag('test CipUX::RPC'); my $object0 = CipUX::RPC->new( { cache_dir => 'blib/cache' } ); is_oneref( $object0, '$object0 has a refcount of 1' ); my $otherref0 = $object0; is_refcount( $object0, 2, '$object0 now has 2 references' ); #diag('test CipUX::RPC::Server'); my $object1 = CipUX::RPC::Server->new( { cache_dir => 'blib/cache' } ); is_oneref( $object1, '$object1 has a refcount of 1' ); my $otherref1 = $object1; is_refcount( $object1, 2, '$object1 now has 2 references' ); #diag('test CipUX::RPC::Daemon'); my $object2 = CipUX::RPC::Server::Daemon->new( { cache_dir => 'blib/cache', name => 'cipux_rpcd' } ); is_oneref( $object2, '$object2 has a refcount of 1' ); my $otherref2 = $object2; is_refcount( $object2, 2, '$object2 now has 2 references' ); #diag('test CipUX::RPC::Test::Client'); my $object3 = CipUX::RPC::Test::Client->new( { name => 'cipux_rpc_test_client', cache_dir => 'blib/cache' } ); is_oneref( $object3, '$object3 has a refcount of 1' ); my $otherref = $object3; is_refcount( $object3, 2, '$object3 now has 2 references' ); CipUX-RPC-3.4.0.9/t/perlcritic.t000444001750001750 43611432067074 17010 0ustar00ckuelkerckuelker000000000000#!perl use strict; use warnings; use Test::More; use English qw(-no_match_vars); eval { require Test::Perl::Critic; }; if ($EVAL_ERROR) { my $msg = 'Test::Perl::Critic required to for testing PBP compliance'; plan( skip_all => $msg ); } Test::Perl::Critic::all_critic_ok();