From 7bb1a118f3f05defbb44edba93f7ee97f7b5eeaa Mon Sep 17 00:00:00 2001 From: janw Date: Fri, 18 Jan 2008 07:53:55 +0000 Subject: [PATCH] Added POE Version. Need to install 'libpoe-perl'. git-svn-id: https://oss.gonicus.de/repositories/gosa/trunk@8486 594d385d-05f5-0310-b6e9-bd551577e9d8 --- gosa-si-poe/README | 29 + gosa-si-poe/arp-handler-d | 563 ++++++++ gosa-si-poe/arp-handler-d.cfg | 14 + gosa-si-poe/bus.conf | 13 + gosa-si-poe/client.conf | 13 + gosa-si-poe/debian/README.debian | 11 + gosa-si-poe/debian/changelog | 6 + gosa-si-poe/debian/compat | 1 + gosa-si-poe/debian/control | 40 + gosa-si-poe/debian/copyright | 8 + gosa-si-poe/debian/gosa-si | 2 + gosa-si-poe/debian/gosa-si-client.dirs | 3 + gosa-si-poe/debian/gosa-si-client.install | 2 + gosa-si-poe/debian/gosa-si-client.postinst | 15 + gosa-si-poe/debian/gosa-si-common.dirs | 2 + gosa-si-poe/debian/gosa-si-common.install | 3 + gosa-si-poe/debian/gosa-si-common.postinst | 15 + gosa-si-poe/debian/gosa-si-server.dirs | 4 + gosa-si-poe/debian/gosa-si-server.init | 91 ++ gosa-si-poe/debian/gosa-si-server.install | 7 + gosa-si-poe/debian/rules | 83 ++ gosa-si-poe/gosa-si-bus | 1362 +++++++++++++++++++ gosa-si-poe/gosa-si-client | 1363 ++++++++++++++++++++ gosa-si-poe/gosa-si-server | 1082 ++++++++++++++++ gosa-si-poe/modules/DBsqlite.pm | 377 ++++++ gosa-si-poe/modules/GosaPackages.pm | 610 +++++++++ gosa-si-poe/modules/GosaSupportDaemon.pm | 300 +++++ gosa-si-poe/modules/ServerPackages.pm | 890 +++++++++++++ gosa-si-poe/modules/TestModule.pm | 76 ++ gosa-si-poe/server.conf | 32 + gosa-si-poe/server/events/ping | 26 + gosa-si-poe/tests/client.php | 43 + gosa-si-poe/tests/sqlite-check.pl | 95 ++ gosa-si-poe/tests/testGOsa.pl | 107 ++ 34 files changed, 7288 insertions(+) create mode 100644 gosa-si-poe/README create mode 100755 gosa-si-poe/arp-handler-d create mode 100644 gosa-si-poe/arp-handler-d.cfg create mode 100644 gosa-si-poe/bus.conf create mode 100644 gosa-si-poe/client.conf create mode 100644 gosa-si-poe/debian/README.debian create mode 100644 gosa-si-poe/debian/changelog create mode 100644 gosa-si-poe/debian/compat create mode 100644 gosa-si-poe/debian/control create mode 100644 gosa-si-poe/debian/copyright create mode 100644 gosa-si-poe/debian/gosa-si create mode 100644 gosa-si-poe/debian/gosa-si-client.dirs create mode 100644 gosa-si-poe/debian/gosa-si-client.install create mode 100644 gosa-si-poe/debian/gosa-si-client.postinst create mode 100644 gosa-si-poe/debian/gosa-si-common.dirs create mode 100644 gosa-si-poe/debian/gosa-si-common.install create mode 100644 gosa-si-poe/debian/gosa-si-common.postinst create mode 100644 gosa-si-poe/debian/gosa-si-server.dirs create mode 100755 gosa-si-poe/debian/gosa-si-server.init create mode 100644 gosa-si-poe/debian/gosa-si-server.install create mode 100755 gosa-si-poe/debian/rules create mode 100755 gosa-si-poe/gosa-si-bus create mode 100755 gosa-si-poe/gosa-si-client create mode 100755 gosa-si-poe/gosa-si-server create mode 100644 gosa-si-poe/modules/DBsqlite.pm create mode 100644 gosa-si-poe/modules/GosaPackages.pm create mode 100644 gosa-si-poe/modules/GosaSupportDaemon.pm create mode 100644 gosa-si-poe/modules/ServerPackages.pm create mode 100644 gosa-si-poe/modules/TestModule.pm create mode 100644 gosa-si-poe/server.conf create mode 100755 gosa-si-poe/server/events/ping create mode 100755 gosa-si-poe/tests/client.php create mode 100755 gosa-si-poe/tests/sqlite-check.pl create mode 100644 gosa-si-poe/tests/testGOsa.pl diff --git a/gosa-si-poe/README b/gosa-si-poe/README new file mode 100644 index 000000000..e8c755530 --- /dev/null +++ b/gosa-si-poe/README @@ -0,0 +1,29 @@ + +/usr/share/perl5/GOSA +common: +- GosaSupportDaemon.pm +- DBsqlite.pm + +/usr/lib/gosa-si/modules +server-module: +- ArpPackages.pm +- GosaPackages.pm +- ServerPackages.pm +- FAIPackages.pm + +server-events: +/usr/lib/gosa-si/server +- ping + +client-events: +/usr/lib/gosa-si/client + +config-files: /etc/gosa-si/ +- bus.conf +- client.conf +- server.conf + +/var/lib/gosa-si +db's: +- jobs.db + diff --git a/gosa-si-poe/arp-handler-d b/gosa-si-poe/arp-handler-d new file mode 100755 index 000000000..b8698bcf7 --- /dev/null +++ b/gosa-si-poe/arp-handler-d @@ -0,0 +1,563 @@ +#!/usr/bin/perl +#=============================================================================== +# +# FILE: gosa-support-daemon.pl +# +# USAGE: ./.gosa-support-daemon.pl +# +# DESCRIPTION: +# +# OPTIONS: --- +# REQUIREMENTS: --- +# BUGS: --- +# NOTES: --- +# AUTHOR: Andreas Rettenberger, +# COMPANY: Gonicus GmbH, Arnsberg +# VERSION: 1.0 +# CREATED: 21.08.2007 15:13:51 CEST +# REVISION: --- +#=============================================================================== + +use strict; +use warnings; +use Getopt::Long; +use Config::IniFiles; +use POSIX; +use Fcntl; +use Net::LDAP; +use Net::LDAP::LDIF; +use Net::LDAP::Entry; +use Switch; + + +my ($verbose, $cfg_file, $log_file, $pid_file, $foreground); +my ($timeout, $mailto, $mailfrom, $user, $group); +my ($procid, $pid, $loglevel); +my ($fifo_path, $max_process_timeout, $max_process ); +my %daemon_children; +my ($ldap, $bind_phrase, $password, $ldap_base) ; + +$procid = -1 ; +$foreground = 0 ; +$verbose = 0 ; +$max_process = 2 ; +$max_process_timeout = 1 ; +$ldap_base = "dc=gonicus,dc=de" ; +#$ldap_path = "/var/run/gosa-support-daemon.socket"; +#$log_path = "/var/log/gosa-support-daemon.log"; +#$pid_path = "/var/run/gosa-support-daemon/gosa-support-daemon.pid"; + +#--------------------------------------------------------------------------- +# parse commandline options +#--------------------------------------------------------------------------- +Getopt::Long::Configure( "bundling" ); +GetOptions( "v|verbose+" => \$verbose, + "c|config=s" => \$cfg_file, + "h|help" => \&usage, + "l|logfile=s" => \$log_file, + "p|pid=s" => \$pid_file, + "f|foreground" => \$foreground); + +#--------------------------------------------------------------------------- +# read and set config parameters +#--------------------------------------------------------------------------- +my %cfg_defaults = +("Allgemein" => + {"timeout" => [ \$timeout, 1000 ], + "mailto" => [ \$mailto, 'root@localhost' ], + "mailfrom" => [ \$mailfrom, 'sps-daemon@localhost' ], + "user" => [ \$user, "nobody" ], + "group" => [ \$group, "nogroup" ], + "fifo_path" => [ \$fifo_path, "/home/rettenbe/gonicus/gosa-support/tmp/fifo" ], + "log_file" => [ \$log_file, "/home/rettenbe/gonicus/gosa-support/tmp/gosa-support.log" ], + "pid_file" => [ \$pid_file, "/home/rettenbe/gonicus/gosa-support/tmp/gosa-support.pid" ], + "loglevel" => [ \$loglevel, 1] + }, +"LDAP" => + {"bind" => [ \$bind_phrase, "cn=ldapadmin,dc=gonicus,dc=de" ], + "password" => [ \$password, "tester" ], + } + ); +&read_configfile; + + +#=== FUNCTION ================================================================ +# NAME: check_cmdline_param +# PURPOSE: checks all commandline parameters to validity +# PARAMETERS: none +# RETURNS: none +# DESCRIPTION: ???? +# THROWS: no exceptions +# COMMENTS: none +# SEE ALSO: n/a +#=============================================================================== +sub check_cmdline_param () { + my $err_config; + my $err_log; + my $err_pid; + my $err_counter = 0; + if( not defined( $cfg_file)) { + $err_config = "please specify a config file"; + $err_counter += 1; + } + if( not defined( $log_file)) { + $err_log = "please specify a log file"; + $err_counter += 1; + } + if( not defined( $pid_file)) { + $err_pid = "please specify a pid file"; + $err_counter += 1; + } + if( $err_counter > 0 ) { + &usage( "", 1 ); + if( defined( $err_config)) { print STDERR "$err_config\n"} + if( defined( $err_log)) { print STDERR "$err_log\n" } + if( defined( $err_pid)) { print STDERR "$err_pid\n"} + print STDERR "\n"; + exit( -1 ); + } +} + +#=== FUNCTION ================================================================ +# NAME: check_pid +# PURPOSE: +# PARAMETERS: none +# RETURNS: none +# DESCRIPTION: ???? +# THROWS: no exceptions +# COMMENTS: none +# SEE ALSO: n/a +#=============================================================================== +sub check_pid { + if( open( LOCK_FILE, "<$pid_file") ) { + $procid = ; + if( defined $procid ) { + chomp( $procid ); + if( -f "/proc/$procid/stat" ) { + my($stat) = `cat /proc/$procid/stat` =~ m/$procid \((.+)\).*/; + print "\t".$stat."\n"; + if( "sps-daemon.pl" eq $stat ) { + close( LOCK_FILE ); + exit -1; + } + } + } + close( LOCK_FILE ); + unlink( $pid_file ); + } + + # Try to open PID file + if (!sysopen(LOCK_FILE, $pid_file, O_WRONLY|O_CREAT|O_EXCL, 0644)) { + my($msg) = "Couldn't obtain lockfile '$pid_file' "; + if (open(LOCK_FILE, "<", $pid_file) && ($pid = )) { + chomp($pid); + $msg .= "(PID $pid)\n"; + } else { + $msg .= "(unable to read PID)\n"; + } + if ( ! $foreground ) { + daemon_log( $msg."\n"); + } else { + print( STDERR " $msg " ); + } + exit( -1 ); + } +} + +#=== FUNCTION ================================================================ +# NAME: read_configfile +# PURPOSE: read the configuration file and provide the programm with +# parameters +# PARAMETERS: none +# RETURNS: none +# DESCRIPTION: ???? +# THROWS: no exceptions +# COMMENTS: none +# SEE ALSO: n/a +#=============================================================================== +sub read_configfile { + my $log_time = localtime(time); + my $cfg; + if( defined( $cfg_file) && ( length($cfg_file) > 0 )) { + if( -r $cfg_file ) { + $cfg = Config::IniFiles->new( -file => $cfg_file ); + } else { + usage( "Couldn't read config file: $cfg_file \n" ); + } + } else { + $cfg = Config::IniFiles->new() ; + } + + foreach my $section (keys %cfg_defaults) { # "Parse" config into values + foreach my $param (keys %{$cfg_defaults{ $section }}) { + my $pinfo = $cfg_defaults{ $section }{ $param }; + ${@$pinfo[ 0 ]} = $cfg->val( $section, $param, @$pinfo[ 1 ] ); + } + } + + if(-e $log_file ) { unlink $log_file } + daemon_log("$log_time: config file read\n"); +} + +#=== FUNCTION ================================================================ +# NAME: daemon_log +# PURPOSE: log messages to specified logfile +# PARAMETERS: $msg, $level +# RETURNS: ???? +# DESCRIPTION: Takes a message ($msg) and append it to the logfile. The +# standard log-level ($level) is 1. Messages whith higher level +# than the verbosity-level (defined by commandline) are printed +# out to commandline. Messages with log-level lower than 2 are +# not logged to logfile! +# THROWS: no exceptions +# COMMENTS: none +# SEE ALSO: n/a +#=============================================================================== +sub daemon_log { + my( $msg, $level ) = @_; + if(not defined $msg) { return } + if(not defined $level) { $level = 1 } + open(LOG_HANDLE, ">>$log_file"); + if(not defined open( LOG_HANDLE, ">>$log_file" ) ) { return } + chomp($msg); + #if( $verbose >= $level ) { print "$msg"."\n" } + if( $level <= 1 ) { print LOG_HANDLE $msg."\n" } + if( $foreground ) { print $msg."\n" } + close( LOG_HANDLE ); + } + +#=== FUNCTION ================================================================ +# NAME: signal handler +# PURPOSE: catches signals from the programm and do diffrent things +# than default +# PARAMETERS: none +# RETURNS: none +# DESCRIPTION: sighandler +# THROWS: no exceptions +# COMMENTS: none +# SEE ALSO: n/a +#=============================================================================== +sub sigINT { + my $log_time = localtime(time); + print "INT\n"; + if( -p $fifo_path ) { + close FIFO ; + unlink($fifo_path) ; + daemon_log( "$log_time: FIFO closed after signal INT!\n") ; + } + if(defined($ldap)) { + $ldap->unbind; + } + $SIG{INT} = "DEFAULT" ; + kill INT => $$ ; +} +$SIG{INT} = \&sigINT ; + +#=== FUNCTION ================================================================ +# NAME: usage +# PURPOSE: +# PARAMETERS: none +# RETURNS: none +# DESCRIPTION: print out the usage of the program +# THROWS: no exceptions +# COMMENTS: none +# SEE ALSO: n/a +#=============================================================================== +sub usage { + my( $text, $help ) = @_; + $text = undef if( "h" eq $text ); + (defined $text) && print STDERR "\n$text\n"; + if( (defined $help && $help) || (!defined $help && !defined $text) ) { + print STDERR << "EOF" ; +usage: $0 [-hvf] [-c config, -l logfile, -p pidfile] + + -h : this (help) message + -c : config file + -l : log file (example: /var/log/sps/sps.log) + -p : pid file (example: /var/run/sps/sps.pid) + -f : foreground (don"t fork) + -v : be verbose (multiple to increase verbosity) +EOF + } + print "\n" ; +} + + +#=== FUNCTION ================================================================ +# NAME: open_fifo +# PURPOSE: +# PARAMETERS: $fifo_path +# RETURNS: 0: FIFO couldn"t be setup, 1: FIFO setup correctly +# DESCRIPTION: creates a FIFO at $fifo_path +# THROWS: no exceptions +# COMMENTS: none +# SEE ALSO: n/a +#=============================================================================== +sub open_fifo { + my ($fifo_path) = @_ ; + my $log_time = localtime( time ); + if( -p $fifo_path ) { + daemon_log("$log_time: FIFO at $fifo_path already exists\n"); + return 0; + } + POSIX::mkfifo($fifo_path, 0666) or die "can't mkfifo $fifo_path: $!"; + daemon_log( "$log_time: FIFO started at $fifo_path\n" ) ; + return 1; + } + + +#=== FUNCTION ================================================================ +# NAME: add_ldap_entry +# PURPOSE: adds an element to ldap-tree +# PARAMETERS: +# RETURNS: none +# DESCRIPTION: ???? +# THROWS: no exceptions +# COMMENTS: none +# SEE ALSO: n/a +#=============================================================================== +sub add_ldap_entry { + my ($ldap_tree, $ldap_base, $mac, $gotoSysStatus, $ip, $interface, $desc) = @_; + my $dn = "cn=$mac,ou=incoming,$ldap_base"; + my $s_res = &search_ldap_entry($ldap_tree, $ldap_base, "(|(macAddress=$mac)(dhcpHWAddress=ethernet $mac))"); + my $c_res = $s_res->count; + if($c_res == 1) { + daemon_log("WARNING: macAddress $mac already in LDAP", 1); + return; + } elsif($c_res > 0) { + daemon_log("ERROR: macAddress $mac exists $c_res times in LDAP", 1); + return; + } + + # create LDAP entry + my $entry = Net::LDAP::Entry->new( $dn ); + $entry->dn($dn); + $entry->add("objectClass" => "goHard"); + $entry->add("cn" => $mac); + $entry->add("macAddress" => $mac); + if(defined $gotoSysStatus) {$entry->add("gotoSysStatus" => $gotoSysStatus)} + if(defined $ip) {$entry->add("ipHostNumber" => $ip) } + #if(defined $interface) { } + if(defined $desc) {$entry->add("description" => $desc) } + + # submit entry to LDAP + my $result = $entry->update ($ldap_tree); + + # for $result->code constants please look at Net::LDAP::Constant + my $log_time = localtime( time ); + if($result->code == 68) { # entry already exists + daemon_log("WARNING: $log_time: $dn ".$result->error, 3); + } elsif($result->code == 0) { # everything went fine + daemon_log("$log_time: add entry $dn to ldap", 1); + } else { # if any other error occur + daemon_log("ERROR: $log_time: $dn, ".$result->code.", ".$result->error, 1); + } + return; +} + + +#=== FUNCTION ================================================================ +# NAME: change_ldap_entry +# PURPOSE: ???? +# PARAMETERS: ???? +# RETURNS: ???? +# DESCRIPTION: ???? +# THROWS: no exceptions +# COMMENTS: none +# SEE ALSO: n/a +#=============================================================================== +sub change_ldap_entry { + my ($ldap_tree, $ldap_base, $mac, $gotoSysStatus ) = @_; + + # check if ldap_entry exists or not + my $s_res = &search_ldap_entry($ldap_tree, $ldap_base, "(|(macAddress=$mac)(dhcpHWAddress=ethernet $mac))"); + my $c_res = $s_res->count; + if($c_res == 0) { + daemon_log("WARNING: macAddress $mac not in LDAP", 1); + return; + } elsif($c_res > 1) { + daemon_log("ERROR: macAddress $mac exists $c_res times in LDAP", 1); + return; + } + + my $s_res_entry = $s_res->pop_entry(); + my $dn = $s_res_entry->dn(); + my $result = $ldap->modify( $dn, replace => {'gotoSysStatus' => $gotoSysStatus } ); + + # for $result->code constants please look at Net::LDAP::Constant + my $log_time = localtime( time ); + if($result->code == 32) { # entry doesnt exists + &add_ldap_entry($mac, $gotoSysStatus); + } elsif($result->code == 0) { # everything went fine + daemon_log("$log_time: entry $dn changed successful", 1); + } else { # if any other error occur + daemon_log("ERROR: $log_time: $dn, ".$result->code.", ".$result->error, 1); + } + + return; +} + +#=== FUNCTION ================================================================ +# NAME: search_ldap_entry +# PURPOSE: ???? +# PARAMETERS: [Net::LDAP] $ldap_tree - object of an ldap-tree +# string $sub_tree - dn of the subtree the search is performed +# string $search_string - either a string or a Net::LDAP::Filter object +# RETURNS: [Net::LDAP::Search] $msg - result object of the performed search +# DESCRIPTION: ???? +# THROWS: no exceptions +# COMMENTS: none +# SEE ALSO: n/a +#=============================================================================== +sub search_ldap_entry { + my ($ldap_tree, $sub_tree, $search_string) = @_; + my $msg = $ldap_tree->search( # perform a search + base => $sub_tree, + filter => $search_string, + ) or daemon_log("cannot perform search at ldap: $@", 1); +# if(defined $msg) { +# print $sub_tree."\t".$search_string."\t"; +# print $msg->count."\n"; +# foreach my $entry ($msg->entries) { $entry->dump; }; +# } + + return $msg; +} + + + +#========= MAIN = main ======================================================== +daemon_log( "####### START DAEMON ######\n", 1 ); +&check_cmdline_param ; +&check_pid; +&open_fifo($fifo_path); + +# Just fork, if we"re not in foreground mode +if( ! $foreground ) { $pid = fork(); } +else { $pid = $$; } + +# Do something useful - put our PID into the pid_file +if( 0 != $pid ) { + open( LOCK_FILE, ">$pid_file" ); + print LOCK_FILE "$pid\n"; + close( LOCK_FILE ); + if( !$foreground ) { exit( 0 ) }; +} + + +if( not -p $fifo_path ) { die "fifo file disappeared\n" } +sysopen(FIFO, $fifo_path, O_RDONLY) or die "can't read from $fifo_path: $!" ; + +while( 1 ) { + # checke alle prozesse im hash daemon_children ob sie noch aktiv sind, wenn + # nicht, dann entferne prozess aus hash + while( (my $key, my $val) = each( %daemon_children) ) { + my $status = waitpid( $key, &WNOHANG) ; + if( $status == -1 ) { + delete $daemon_children{$key} ; + daemon_log("childprocess finished: $key", 3) ; + } + } + + # ist die max_process anzahl von prozesskindern erreicht, dann warte und + # prüfe erneut, ob in der zwischenzeit prozesse fertig geworden sind + if( keys( %daemon_children ) >= $max_process ) { + sleep($max_process_timeout) ; + next ; + } + + my $msg = ; + if( not defined( $msg )) { next ; } + + chomp( $msg ); + if( length( $msg ) == 0 ) { next ; } + + my $forked_pid = fork(); +#=== PARENT = parent ========================================================== + if ( $forked_pid != 0 ) { + daemon_log("childprocess forked: $forked_pid", 3) ; + $daemon_children{$forked_pid} = 0 ; + } +#=== CHILD = child ============================================================ + else { + # parse the incoming message from arp, split the message and return + # the values in an array. not defined values are set to "none" + #my ($mac, $ip, $interface, $arp_sig, $desc) = &parse_input( $msg ) ; + daemon_log( "childprocess read from arp: $fifo_path\nline: $msg", 3); + my ($mac, $ip, $interface, $arp_sig, $desc) = split('\s', $msg, 5); + + # create connection to LDAP + $ldap = Net::LDAP->new( "localhost" ) or die "$@"; + $ldap->bind($bind_phrase, + password => $password, + ) ; + + switch($arp_sig) { + case 0 {&change_ldap_entry($ldap, $ldap_base, + $mac, "ip-changed", + )} + case 1 {&change_ldap_entry($ldap, $ldap_base, + $mac, "mac-not-whitelisted", + )} + case 2 {&change_ldap_entry($ldap, $ldap_base, + $mac, "mac-in-blacklist", + )} + case 3 {&add_ldap_entry($ldap, $ldap_base, + $mac, "new-mac-address", $ip, + $interface, $desc, + )} + case 4 {&change_ldap_entry($ldap, $ldap_base, + $mac, "unauthorized-arp-request", + )} + case 5 {&change_ldap_entry($ldap, $ldap_base, + $mac, "abusive-number-of-arp-requests", + )} + case 6 {&change_ldap_entry($ldap, $ldap_base, + $mac, "ether-and-arp-mac-differs", + )} + case 7 {&change_ldap_entry($ldap, $ldap_base, + $mac, "flood-detected", + )} + case 8 {&add_ldap_entry($ldap, $ldap_base, + $mac, $ip, "new-system", + )} + case 9 {&change_ldap_entry($ldap, $ldap_base, + $mac, "mac-changed", + )} + } + + + # ldap search +# my $base_phrase = "dc=gonicus,dc=de"; +# my $filter_phrase = "cn=keinesorge"; +# my $attrs_phrase = "cn macAdress"; +# my $msg_search = $ldap->search( base => $base_phrase, +# filter => $filter_phrase, +# attrs => $attrs_phrase, +# ); +# $msg_search->code && die $msg_search->error; +# +# my @entries = $msg_search->entries; +# my $max = $msg_search->count; +# print "anzahl der entries: $max\n"; +# my $i; +# for ( $i = 0 ; $i < $max ; $i++ ) { +# my $entry = $msg_search->entry ( $i ); +# foreach my $attr ( $entry->attributes ) { +# if( not $attr eq "cn") { +# next; +# } +# print join( "\n ", $attr, $entry->get_value( $attr ) ), "\n\n"; +# } +# } + + # ldap add + + + $ldap->unbind; + exit; + } + +} + + diff --git a/gosa-si-poe/arp-handler-d.cfg b/gosa-si-poe/arp-handler-d.cfg new file mode 100644 index 000000000..36c24a35b --- /dev/null +++ b/gosa-si-poe/arp-handler-d.cfg @@ -0,0 +1,14 @@ +[Allgemein] +timeout = 1000 +mailto = root@localhost +mailfrom = gosa-sd@localhost +user = rettenbe +group = usr +fifo_path = /home/rettenbe/gonicus/projekte/gosa-trunk/contrib/daemon/fifo +log_file = /home/rettenbe/gonicus/projekte/gosa-trunk/contrib/daemon/gosa-sd.log +pid_file = /home/rettenbe/gonicus/projekte/gosa-trunk/contrib/daemon/gosa-sd.pid +loglevel = 1 + +[LDAP] +bind = cn=ldapadmin,dc=gonicus,dc=de +password = tester diff --git a/gosa-si-poe/bus.conf b/gosa-si-poe/bus.conf new file mode 100644 index 000000000..7ca56e906 --- /dev/null +++ b/gosa-si-poe/bus.conf @@ -0,0 +1,13 @@ +[general] +log_file = /var/log/gosa-si-bus.log +pid_file = /var/run/gosa-si-bus.pid +child_max = 10 +child_min = 2 +child_timeout = 10 + +[bus] +bus_activ = on +bus_passwd = secret-bus-password +bus_ip = 127.0.0.1 +bus_port = 20080 + diff --git a/gosa-si-poe/client.conf b/gosa-si-poe/client.conf new file mode 100644 index 000000000..0c65e2cad --- /dev/null +++ b/gosa-si-poe/client.conf @@ -0,0 +1,13 @@ +[general] +log_file = /var/log/gosa-si-client.log +pid_file = /var/run/gosa-si-client.pid + +[client] +client_port = 20083 + +[server] +server_ip = 127.0.0.1 +server_port = 20081 +server_passwd = secret-server-password +server_timeout = 5 +server_domain = intranet.gonicus.de diff --git a/gosa-si-poe/debian/README.debian b/gosa-si-poe/debian/README.debian new file mode 100644 index 000000000..100bd2d6d --- /dev/null +++ b/gosa-si-poe/debian/README.debian @@ -0,0 +1,11 @@ +README.Debian for GOto 3.0 +-------------------------- + +* Configuring GOto 3.0 + +You need a proper LDAP/FAI/GOsa setup to make this run. More +text will follow later. Sorry. + +---- +Cajus Pollmeier Fri 02 Jun 2006 16:23:50 +0200 + diff --git a/gosa-si-poe/debian/changelog b/gosa-si-poe/debian/changelog new file mode 100644 index 000000000..5412238b3 --- /dev/null +++ b/gosa-si-poe/debian/changelog @@ -0,0 +1,6 @@ +gosa-si (1.0-1) unstable; urgency=low + + * Initial release + + -- Cajus Pollmeier Fri, 7 Dec 2007 11:37:45 +0100 + diff --git a/gosa-si-poe/debian/compat b/gosa-si-poe/debian/compat new file mode 100644 index 000000000..7ed6ff82d --- /dev/null +++ b/gosa-si-poe/debian/compat @@ -0,0 +1 @@ +5 diff --git a/gosa-si-poe/debian/control b/gosa-si-poe/debian/control new file mode 100644 index 000000000..fd596d22d --- /dev/null +++ b/gosa-si-poe/debian/control @@ -0,0 +1,40 @@ +Source: gosa-si +Section: utils +Priority: optional +Maintainer: Cajus Pollmeier +Standards-Version: 3.7.2.2 +Build-Depends: debhelper(>= 4.2.32), dpatch + +Package: gosa-si-common +Architecture: any +Depends: libconfig-inifiles-perl, libcrypt-rijndael-perl, libxml-simple-perl, libipc-shareable-perl, libdata-dumper-simple-perl, libmime-perl, libdbd-sqlite3-perl, libnet-ldap-perl, libnetaddr-ip-perl +Suggests: gosa-si-server, gosa-si-client +Description: GOsa support infrastructure + This package provides common library functionality used by the + infrastructure server and client packages. + . + GOsa is a combination of system-administrator and end-user web + interface, designed to handle LDAP based setups. + +Package: gosa-si-server +Architecture: any +Depends: gosa-si-common +Suggests: gosa +Description: GOsa support infrastructure server + This package provides everything you need in order to deploy a simple + or distributed GOsa support infrastructure. It can be used to trigger + certain actions or retrieve information from clients. + . + GOsa is a combination of system-administrator and end-user web + interface, designed to handle LDAP based setups. + +Package: gosa-si-client +Architecture: any +Depends: gosa-si-common +Suggests: gosa +Description: GOsa support infrastructure client + This package lets you join to a GOsa support infrastructure as a + client in order to provide information or to act on events. + . + GOsa is a combination of system-administrator and end-user web + interface, designed to handle LDAP based setups. diff --git a/gosa-si-poe/debian/copyright b/gosa-si-poe/debian/copyright new file mode 100644 index 000000000..d7463efe4 --- /dev/null +++ b/gosa-si-poe/debian/copyright @@ -0,0 +1,8 @@ +This package was debianized by Cajus Pollmeier + on Mon, 25 Jun 2007 12:57:35 +0100. + +Copyright: GPL2 + +This code is released under the terms of the GPLv2 license. + +See /usr/share/common-licenses/GPL-2 for the full license. diff --git a/gosa-si-poe/debian/gosa-si b/gosa-si-poe/debian/gosa-si new file mode 100644 index 000000000..10df929a0 --- /dev/null +++ b/gosa-si-poe/debian/gosa-si @@ -0,0 +1,2 @@ +# /etc/default/gosa-si - configure the init script +START_BUS=0 diff --git a/gosa-si-poe/debian/gosa-si-client.dirs b/gosa-si-poe/debian/gosa-si-client.dirs new file mode 100644 index 000000000..2f6b7067d --- /dev/null +++ b/gosa-si-poe/debian/gosa-si-client.dirs @@ -0,0 +1,3 @@ +etc/gosa-si +usr/sbin +usr/lib/gosa-si/client/events diff --git a/gosa-si-poe/debian/gosa-si-client.install b/gosa-si-poe/debian/gosa-si-client.install new file mode 100644 index 000000000..e5544c641 --- /dev/null +++ b/gosa-si-poe/debian/gosa-si-client.install @@ -0,0 +1,2 @@ +gosa-si-client usr/sbin +client.conf etc/gosa-si diff --git a/gosa-si-poe/debian/gosa-si-client.postinst b/gosa-si-poe/debian/gosa-si-client.postinst new file mode 100644 index 000000000..0747c6a3d --- /dev/null +++ b/gosa-si-poe/debian/gosa-si-client.postinst @@ -0,0 +1,15 @@ +#!/bin/sh + +#DEBHELPER# + +# We exit unless the package is being configured +case "$1" in + abort*upgrade) exit 0;; + abort*remove) exit 0;; + abort*deconfigure) exit 0;; + configure) ;; + *) exit 0; +esac + +[ ! -d /usr/lib/gosa-si/client/events ] && install -d -o root -g root -m 750 /usr/lib/gosa-si/client/events + diff --git a/gosa-si-poe/debian/gosa-si-common.dirs b/gosa-si-poe/debian/gosa-si-common.dirs new file mode 100644 index 000000000..2d13e8f10 --- /dev/null +++ b/gosa-si-poe/debian/gosa-si-common.dirs @@ -0,0 +1,2 @@ +/etc/default +/usr/share/perl5/GOSA diff --git a/gosa-si-poe/debian/gosa-si-common.install b/gosa-si-poe/debian/gosa-si-common.install new file mode 100644 index 000000000..3db70042e --- /dev/null +++ b/gosa-si-poe/debian/gosa-si-common.install @@ -0,0 +1,3 @@ +modules/GosaSupportDaemon.pm usr/share/perl5/GOSA +modules/DBsqlite.pm usr/share/perl5/GOSA +debian/gosa-si etc/default diff --git a/gosa-si-poe/debian/gosa-si-common.postinst b/gosa-si-poe/debian/gosa-si-common.postinst new file mode 100644 index 000000000..3d2b7723d --- /dev/null +++ b/gosa-si-poe/debian/gosa-si-common.postinst @@ -0,0 +1,15 @@ +#!/bin/sh + +#DEBHELPER# + +# We exit unless the package is being configured +case "$1" in + abort*upgrade) exit 0;; + abort*remove) exit 0;; + abort*deconfigure) exit 0;; + configure) ;; + *) exit 0; +esac + +[ -d /var/lib/gosa-si ] || install -d -o root -g root -m 750 /var/lib/gosa-si + diff --git a/gosa-si-poe/debian/gosa-si-server.dirs b/gosa-si-poe/debian/gosa-si-server.dirs new file mode 100644 index 000000000..be099ffcf --- /dev/null +++ b/gosa-si-poe/debian/gosa-si-server.dirs @@ -0,0 +1,4 @@ +usr/sbin +usr/lib/gosa-si/modules +usr/lib/gosa-si/server/events +etc/gosa-si diff --git a/gosa-si-poe/debian/gosa-si-server.init b/gosa-si-poe/debian/gosa-si-server.init new file mode 100755 index 000000000..18e68aefc --- /dev/null +++ b/gosa-si-poe/debian/gosa-si-server.init @@ -0,0 +1,91 @@ +#!/bin/sh +# Start/stop the GOsa support daemon infrastructure. +# +### BEGIN INIT INFO +# Provides: gosa-si +# Required-Start: $syslog $time +# Required-Stop: $syslog $time +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +# Short-Description: GOsa message bus and server component +# Description: gosa-si establishes the communication between a couple of +# GOsa hosting servers and optionally clients to do event +# signaling for all communication partners. +### END INIT INFO + +# Sanity checks +test -f /usr/sbin/gosa-si-server || exit 0 +test -f /usr/sbin/gosa-si-bus || exit 0 + +# Load defaults +START_BUS=0 +[ -r /etc/default/gosa-si ] && . /etc/default/gosa-si + +# Load LSB support functions +. /lib/lsb/init-functions + + +start_bus() { + start-stop-daemon --start --quiet --pidfile /var/run/gosa-si-bus.pid --name gosa-si-bus --startas /usr/sbin/gosa-si-bus -- +} + + +start_server() { + start-stop-daemon --start --quiet --pidfile /var/run/gosa-si-server.pid --name gosa-si-server --startas /usr/sbin/gosa-si-server -- -vvvvv $1 +} + + +stop_bus() { + start-stop-daemon --stop --retry 5 --quiet --pidfile /var/run/gosa-si-bus.pid --name gosa-si-bus +} + + +stop_server() { + start-stop-daemon --stop --retry 5 --quiet --pidfile /var/run/gosa-si-server.pid --name gosa-si-server +} + + +case "$1" in +start) log_daemon_msg "Starting GOsa support infrastructure" + if [ "$START_BUS" == "1" ]; then + log_progress_msg "bus" + start_bus + log_progress_msg "daemon" + start_server + else + log_progress_msg "daemon" + start_server --no-bus + fi + log_end_msg $? + ;; +stop) log_daemon_msg "Stopping GOsa support infrastructure" + if [ "$START_BUS" == "1" ]; then + log_progress_msg "daemon" + stop_server + log_progress_msg "bus" + stop_bus + else + log_progress_msg "daemon" + stop_server + fi + log_end_msg $? + ;; +reload|force-reload|restart) log_daemon_msg "Restarting GOsa support infrastructure" + if [ "$START_BUS" == "1" ]; then + stop_server + stop_bus + start_bus + start_server --no-bus + log_progress_msg "done" + else + stop_server + start_server --no-bus + log_progress_msg "done" + fi + log_end_msg $? + ;; +*) log_action_msg "Usage: /etc/init.d/gosa-si {start|stop|restart|reload|force-reload}" + exit 2 + ;; +esac +exit 0 diff --git a/gosa-si-poe/debian/gosa-si-server.install b/gosa-si-poe/debian/gosa-si-server.install new file mode 100644 index 000000000..b7c600f74 --- /dev/null +++ b/gosa-si-poe/debian/gosa-si-server.install @@ -0,0 +1,7 @@ +gosa-si-server usr/sbin +gosa-si-bus usr/sbin +server.conf etc/gosa-si +bus.conf etc/gosa-si +modules/ServerPackages.pm usr/lib/gosa-si/modules +modules/GosaPackages.pm usr/lib/gosa-si/modules +server/events/ping usr/lib/gosa-si/server/events diff --git a/gosa-si-poe/debian/rules b/gosa-si-poe/debian/rules new file mode 100755 index 000000000..f54d5592a --- /dev/null +++ b/gosa-si-poe/debian/rules @@ -0,0 +1,83 @@ +#!/usr/bin/make -f +# Sample debian/rules that uses debhelper. GNU copyright 1997 by Joey Hess. + +# Uncomment this to turn on verbose mode. +#export DH_VERBOSE=1 + +build: patch + #******************************************************** + #* Building ltsp-goto into a Debian/GNU Linux Package * + #* please stand by * + #******************************************************** + +clean: clean-patched unpatch +clean-patched: + dh_testdir + rm -f install-stamp + -rm -f debian/files + -rm -rf debian/tmp + -rm -f debian/substvars + dh_clean + +unpatch: + dpatch deapply-all + rm -rf patch-stamp debian/patched + +install: install-stamp +install-stamp: + dh_testdir + dh_testroot + dh_clean -k + dh_installdirs + + # Create a copy, remove svn stuff + -mkdir debian/tmp + -for i in *; do \ + cp -R $$i debian/tmp ; \ + done + -find debian/tmp -name '*.svn' -type d -exec rm -rf {} \; 2> /dev/null + + -for i in gosa-si-client gosa-si-server modules/GosaPackages.pm modules/ServerPackages.pm modules/TestModule.pm server/events/*; do sed -i 's/use GosaSupportDaemon;/use GOSA::GosaSupportDaemon;/g;s/use DBsqlite;/use GOSA::DBsqlite;/g' debian/tmp/$$i; done + + -sed -i 's!"/etc/gosa-si/modules";!use lib "/usr/lib/gosa-si/modules";!g' debian/tmp/gosa-si-server + + -sed -i 's!"/etc/gosa-si/server/events";!"/usr/lib/gosa-si/server/events";!g' debian/tmp/modules/GosaPackages.pm + + touch install-stamp + +patch: patch-stamp +patch-stamp: + dpatch apply-all + dpatch cat-all >patch-stamp + +binary-indep: install + dh_testdir + dh_testroot + + dh_install --sourcedir=debian/tmp + dh_installdocs + dh_installcron + dh_installexamples + dh_installchangelogs + #dh_installdebconf + dh_installinit -pgosa-si-server --init-script=gosa-si + dh_link + dh_strip + dh_compress + dh_fixperms + dh_perl + dh_installdeb + dh_shlibdeps + + dh_gencontrol + dh_md5sums + dh_builddeb + +source diff: + @echo >&2 'source and diff are obsolete - use dpkg-source -b'; false + +binary: binary-indep +.PHONY: build install clean binary-indep binary + +binary-arch: + diff --git a/gosa-si-poe/gosa-si-bus b/gosa-si-poe/gosa-si-bus new file mode 100755 index 000000000..a5d50f1d2 --- /dev/null +++ b/gosa-si-poe/gosa-si-bus @@ -0,0 +1,1362 @@ +#!/usr/bin/perl +#=============================================================================== +# +# FILE: gosa-server +# +# USAGE: ./gosa-server +# +# DESCRIPTION: +# +# OPTIONS: --- +# REQUIREMENTS: --- +# BUGS: --- +# NOTES: +# AUTHOR: (Andreas Rettenberger), +# COMPANY: +# VERSION: 1.0 +# CREATED: 12.09.2007 08:54:41 CEST +# REVISION: --- +#=============================================================================== + +use strict; +use warnings; +use Getopt::Long; +use Config::IniFiles; +use POSIX; +use Time::HiRes qw( gettimeofday ); + +use IO::Socket::INET; +use Crypt::Rijndael; +use MIME::Base64; +use Digest::MD5 qw(md5 md5_hex md5_base64); +use XML::Simple; +use Data::Dumper; +use Sys::Syslog qw( :DEFAULT setlogsock); +use Cwd; +use File::Spec; +use GOSA::GosaSupportDaemon; +use GOSA::DBsqlite; + +my ($cfg_file, $default_cfg_file, %cfg_defaults, $foreground, $verbose); +my ($bus_activ, $bus_passwd, $bus_ip, $bus_port, $bus_address, $bus, $bus_mac_address, $network_interface); +my ($pid_file, $procid, $pid, $log_file, $my_own_address); +my (%free_child, %busy_child, $child_max, $child_min, %child_alive_time, $child_timeout); +my ($bus_known_server_db, $bus_known_server_file_name); +my ($xml, $bus_cipher); + +$foreground = 0 ; + +%cfg_defaults = +("general" => + {"log_file" => [\$log_file, "/var/run/".$0.".log"], + "pid_file" => [\$pid_file, "/var/run/".$0.".pid"], + "child_max" => [\$child_max, 10], + "child_min" => [\$child_min, 3], + "child_timeout" => [\$child_timeout, 180], + "bus_known_server_file_name" => [\$bus_known_server_file_name, "/var/lib/gosa-si/bus_known_server.db"] + }, +"bus" => + {"bus_activ" => [\$bus_activ, "on"], + "bus_passwd" => [\$bus_passwd, ""], + "bus_ip" => [\$bus_ip, "0.0.0.0"], + "bus_port" => [\$bus_port, "20080"], + } + ); + +#=== FUNCTION ================================================================ +# NAME: read_configfile +# PARAMETERS: cfg_file - string - +# RETURNS: nothing +# DESCRIPTION: read cfg_file and set variables +#=============================================================================== +sub read_configfile { + my $cfg; + if( defined( $cfg_file) && ( length($cfg_file) > 0 )) { + if( -r $cfg_file ) { + $cfg = Config::IniFiles->new( -file => $cfg_file ); + } else { + print STDERR "Couldn't read config file!"; + } + } else { + $cfg = Config::IniFiles->new() ; + } + foreach my $section (keys %cfg_defaults) { + foreach my $param (keys %{$cfg_defaults{ $section }}) { + my $pinfo = $cfg_defaults{ $section }{ $param }; + ${@$pinfo[ 0 ]} = $cfg->val( $section, $param, @$pinfo[ 1 ] ); + } + } +} + +#=== FUNCTION ================================================================ +# NAME: logging +# PARAMETERS: level - string - default 'info' +# msg - string - +# facility - string - default 'LOG_DAEMON' +# RETURNS: nothing +# DESCRIPTION: function for logging +#=============================================================================== +sub daemon_log { + my( $msg, $level ) = @_; + if(not defined $msg) { return } + if(not defined $level) { $level = 1 } + if(defined $log_file){ + open(LOG_HANDLE, ">>$log_file"); + if(not defined open( LOG_HANDLE, ">>$log_file" )) { + print STDERR "cannot open $log_file: $!"; + return } + chomp($msg); + if($level && $verbose && $level <= $verbose){ + print LOG_HANDLE $msg."\n"; + if(defined $foreground) { print $msg."\n" } + } + } + close( LOG_HANDLE ); +# my ($msg, $level, $facility) = @_; +# if(not defined $msg) {return} +# if(not defined $level) {$level = "info"} +# if(not defined $facility) {$facility = "LOG_DAEMON"} +# openlog($0, "pid,cons,", $facility); +# syslog($level, $msg); +# closelog; +# return; +} + +#=== FUNCTION ================================================================ +# NAME: check_cmdline_param +# PARAMETERS: nothing +# RETURNS: nothing +# DESCRIPTION: validates commandline parameter +#=============================================================================== +sub check_cmdline_param () { + my $err_config; + my $err_counter = 0; + if( not defined( $cfg_file)) { + my $cwd = getcwd; + my $name = "/etc/gosa-si/bus.conf"; + $cfg_file = File::Spec->catfile( $cwd, $name ); + } + if( $err_counter > 0 ) { + &usage( "", 1 ); + if( defined( $err_config)) { print STDERR "$err_config\n"} + print STDERR "\n"; + exit( -1 ); + } +} + +#=== FUNCTION ================================================================ +# NAME: check_pid +# PARAMETERS: nothing +# RETURNS: nothing +# DESCRIPTION: handels pid processing +#=============================================================================== +sub check_pid { + $pid = -1; + # Check, if we are already running + if( open(LOCK_FILE, "<$pid_file") ) { + $pid = ; + if( defined $pid ) { + chomp( $pid ); + if( -f "/proc/$pid/stat" ) { + my($stat) = `cat /proc/$pid/stat` =~ m/$pid \((.+)\).*/; + if( $0 eq $stat ) { + close( LOCK_FILE ); + exit -1; + } + } + } + close( LOCK_FILE ); + unlink( $pid_file ); + } + + # create a syslog msg if it is not to possible to open PID file + if (not sysopen(LOCK_FILE, $pid_file, O_WRONLY|O_CREAT|O_EXCL, 0644)) { + my($msg) = "Couldn't obtain lockfile '$pid_file' "; + if (open(LOCK_FILE, '<', $pid_file) + && ($pid = )) + { + chomp($pid); + $msg .= "(PID $pid)\n"; + } else { + $msg .= "(unable to read PID)\n"; + } + if( ! ($foreground) ) { + openlog( $0, "cons,pid", "daemon" ); + syslog( "warning", $msg ); + closelog(); + } + else { + print( STDERR " $msg " ); + } + exit( -1 ); + } +} + + +#=== FUNCTION ================================================================ +# NAME: usage +# PARAMETERS: nothing +# RETURNS: nothing +# DESCRIPTION: print out usage text to STDERR +#=============================================================================== +sub usage { + print STDERR << "EOF" ; +usage: $0 [-hvf] [-c config] + + -h : this (help) message + -c : config file + -f : foreground, process will not be forked to background + -v : be verbose (multiple to increase verbosity) +EOF + print "\n" ; +} + + +#=== FUNCTION ================================================================ +# NAME: sig_int_handler +# PARAMETERS: signal - string - signal arose from system +# RETURNS: noting +# DESCRIPTION: handels tasks to be done befor signal becomes active +#=============================================================================== +sub sig_int_handler { + my ($signal) = @_; + if($bus){ + close($bus); + print "$bus closed\n"; + } + print "$signal\n"; + exit(1); +} +$SIG{INT} = \&sig_int_handler; + +#=== FUNCTION ================================================================ +# NAME: get_interface_for_ip +# PARAMETERS: ip address (i.e. 192.168.0.1) +# RETURNS: array: list of interfaces if ip=0.0.0.0, matching interface if found, undef else +# DESCRIPTION: Uses proc fs (/proc/net/dev) to get list of interfaces. +#=============================================================================== +sub get_interface_for_ip { + my $result; + my $ip= shift; + if ($ip && length($ip) > 0) { + my @ifs= &get_interfaces(); + if($ip eq "0.0.0.0") { + $result = "all"; + } else { + foreach (@ifs) { + my $if=$_; + if(get_ip($if) eq $ip) { + $result = $if; + } + } + } + } + return $result; +} + +#=== FUNCTION ================================================================ +# NAME: get_interfaces +# PARAMETERS: none +# RETURNS: (list of interfaces) +# DESCRIPTION: Uses proc fs (/proc/net/dev) to get list of interfaces. +#=============================================================================== +sub get_interfaces { + my @result; + my $PROC_NET_DEV= ('/proc/net/dev'); + + open(PROC_NET_DEV, "<$PROC_NET_DEV") + or die "Could not open $PROC_NET_DEV"; + + my @ifs = ; + + close(PROC_NET_DEV); + + # Eat first two line + shift @ifs; + shift @ifs; + + chomp @ifs; + foreach my $line(@ifs) { + my $if= (split /:/, $line)[0]; + $if =~ s/^\s+//; + push @result, $if; + } + + return @result; +} + +#=== FUNCTION ================================================================ +# NAME: get_mac +# PARAMETERS: interface name (i.e. eth0) +# RETURNS: (mac address) +# DESCRIPTION: Uses ioctl to get mac address directly from system. +#=============================================================================== +sub get_mac { + my $ifreq= shift; + my $result; + if ($ifreq && length($ifreq) > 0) { + if($ifreq eq "all") { + $result = "00:00:00:00:00:00"; + } else { + my $SIOCGIFHWADDR= 0x8927; # man 2 ioctl_list + + # A configured MAC Address should always override a guessed value + if ($bus_mac_address and length($bus_mac_address) > 0) { + return $bus_mac_address; + } + + socket SOCKET, PF_INET, SOCK_DGRAM, getprotobyname('ip') + or die "socket: $!"; + + if(ioctl SOCKET, $SIOCGIFHWADDR, $ifreq) { + my ($if, $mac)= unpack 'h36 H12', $ifreq; + + if (length($mac) > 0) { + $mac=~ m/^([0-9a-f][0-9a-f])([0-9a-f][0-9a-f])([0-9a-f][0-9a-f])([0-9a-f][0-9a-f])([0-9a-f][0-9a-f])([0-9a-f][0-9a-f])$/; + $mac= sprintf("%s:%s:%s:%s:%s:%s", $1, $2, $3, $4, $5, $6); + $result = $mac; + } + } + } + } + return $result; +} + +#=== FUNCTION ================================================================ +# NAME: get_ip +# PARAMETERS: interface name (i.e. eth0) +# RETURNS: (ip address) +# DESCRIPTION: Uses ioctl to get ip address directly from system. +#=============================================================================== +sub get_ip { + my $ifreq= shift; + my $result= ""; + my $SIOCGIFADDR= 0x8915; # man 2 ioctl_list + my $proto= getprotobyname('ip'); + + socket SOCKET, PF_INET, SOCK_DGRAM, $proto + or die "socket: $!"; + + if(ioctl SOCKET, $SIOCGIFADDR, $ifreq) { + my ($if, $sin) = unpack 'a16 a16', $ifreq; + my ($port, $addr) = sockaddr_in $sin; + my $ip = inet_ntoa $addr; + + if ($ip && length($ip) > 0) { + $result = $ip; + } + } + + return $result; +} + +#=== FUNCTION ================================================================ +# NAME: activating_child +# PARAMETERS: msg - string - incoming message +# host - string - host from which the incomming message comes +# RETURNS: nothing +# DESCRIPTION: handels the distribution of incoming messages to working childs +#=============================================================================== +sub activating_child { + my ($msg, $host) = @_; + my $child = &get_processing_child(); + my $pipe_wr = $$child{'pipe_wr'}; + daemon_log("activating: childpid: $$child{'pid'}", 5); + print $pipe_wr $msg.".".$host."\n"; + return; +} + + +#=== FUNCTION ================================================================ +# NAME: get_processing_child +# PARAMETERS: nothing +# RETURNS: child - hash - holding the process id and the references to the pipe +# handles pipe_wr and pipe_rd +# DESCRIPTION: handels the forking, reactivating and keeping alive tasks +#=============================================================================== +sub get_processing_child { + my $child; + # checking %busy_child{pipe_wr} if msg is 'done', then set child from busy to free + while(my ($key, $val) = each(%busy_child)) { + # check wether process still exists + my $exitus_pid = waitpid($key, WNOHANG); + if($exitus_pid != 0) { + delete $busy_child{$key}; + daemon_log( "prozess:$key wurde aus busy_child entfernt\n", 5); + next; + } + + # check wether process sitll works + my $fh = $$val{'pipe_rd'}; + $fh->blocking(0); + my $child_answer; + if(not $child_answer = <$fh>) { next } + chomp($child_answer); + if($child_answer eq "done") { + delete $busy_child{$key}; + $free_child{$key} = $val; + } + } + + while(my ($key, $val) = each(%free_child)) { + my $exitus_pid = waitpid($key, WNOHANG); + if($exitus_pid != 0) { + delete $free_child{$key}; + daemon_log( "prozess:$key wurde aus free_child entfernt\n", 5); + } + daemon_log("free child:$key\n", 5); + } + # check @free_child and @busy_child + my $free_len = scalar(keys(%free_child)); + my $busy_len = scalar(keys(%busy_child)); + daemon_log("free children $free_len, busy children $busy_len\n",5); + + # if there is a free child, let the child work + if($free_len > 0){ + my @keys = keys(%free_child); + $child = $free_child{$keys[0]}; + if(defined $child) { + $busy_child{$$child{'pid'}} = $child ; + delete $free_child{$$child{'pid'}}; + } + return $child; + } + + # no free child, try to fork another one + if($free_len + $busy_len < $child_max) { + + daemon_log("not enough children, create a new one\n",5); + + # New pipes for communication + my( $PARENT_wr, $PARENT_rd ); + my( $CHILD_wr, $CHILD_rd ); + pipe( $CHILD_rd, $PARENT_wr ); + pipe( $PARENT_rd, $CHILD_wr ); + $PARENT_wr->autoflush(1); + $CHILD_wr->autoflush(1); + + ############ + # fork child + ############ + my $child_pid = fork(); + + #CHILD + if($child_pid == 0) { + # Close unused pipes + close( $CHILD_rd ); + close( $CHILD_wr ); + while( 1 ) { + my $rbits = ""; + vec( $rbits, fileno $PARENT_rd , 1 ) = 1; + + # waiting child_timeout for jobs to do + my $nf = select($rbits, undef, undef, $child_timeout); + if($nf < 0 ) { + # if $nf < 1, error handling + die "select(): $!\n"; + } elsif (! $nf) { + # if already child_min childs are alive, then leave loop + $free_len = scalar(keys(%free_child)); + $busy_len = scalar(keys(%busy_child)); + if($free_len + $busy_len >= $child_min) { + last; + } else { + redo; + } + } + + # a job for a child arise + if ( vec $rbits, fileno $PARENT_rd, 1 ) { + # read everything from pipe + my $msg = ""; + $PARENT_rd->blocking(0); + while(1) { + my $read = <$PARENT_rd>; + if(not defined $read) { last} + $msg .= $read; + } + + # forward the job msg to another function + &process_incoming_msg($msg); + daemon_log("processing of msg finished", 5); + + # important!!! wait until child says 'done', until then child is set from busy to free + print $PARENT_wr "done"; + redo; + } + } + # childs leaving the loop are allowed to die + exit(0); + + #PARENT + } else { + # Close unused pipes + close( $PARENT_rd ); + close( $PARENT_wr ); + # add child to child alive hash + my %child_hash = ( + 'pid' => $child_pid, + 'pipe_wr' => $CHILD_wr, + 'pipe_rd' => $CHILD_rd, + ); + + $child = \%child_hash; + $busy_child{$$child{'pid'}} = $child; + return $child; + } + } +} + + +#=== FUNCTION ================================================================ +# NAME: process_incoming_msg +# PARAMETERS: crypted_msg - string - incoming crypted message +# RETURNS: nothing +# DESCRIPTION: handels the proceeded distribution to the appropriated functions +#=============================================================================== +sub process_incoming_msg { + my ($crypted_msg) = @_; + if(not defined $crypted_msg) { + daemon_log("function 'process_incoming_msg': got no msg", 7); + return; + } + $crypted_msg =~ /^([\s\S]*?)\.(\d{1,3}?)\.(\d{1,3}?)\.(\d{1,3}?)\.(\d{1,3}?)$/; + $crypted_msg = $1; + my $host = sprintf("%s.%s.%s.%s", $2, $3, $4, $5); + + my $msg; + my $msg_hash; + my $host_name; + my $host_key; + + # check wether incoming msg is a new msg + $host_name = $bus_address; + $host_key = $bus_passwd; + daemon_log("process_incoming_msg: host_name: $host_name", 7); + daemon_log("process_incoming_msg: host_key: $host_key", 7); + eval{ + my $key_cipher = &create_ciphering($host_key); + $msg = &decrypt_msg($crypted_msg, $key_cipher); + $msg_hash = &transform_msg2hash($msg); + }; + if($@) { + daemon_log("process_incoming_msg: deciphering raise error", 7); + daemon_log("$@", 8); + $msg = undef; + $msg_hash = undef; + $host_name = undef; + $host_key = undef; + } + + # check wether incoming msg is from a bus_known_server + if( not defined $msg ) { + my $query_res = $bus_known_server_db->select_dbentry( {table=>'bus_known_server'} ); + while( my ($hit_num, $hit) = each %{ $query_res } ) { + $host_name = $hit->{hostname}; + if( not $host_name =~ "^$host") { + next; + } + $host_key = $hit->{hostkey}; + daemon_log("process_incoming_msg: host_name: $host_name", 7); + daemon_log("process_incoming_msg: host_key: $host_key", 7); + eval{ + my $key_cipher = &create_ciphering($host_key); + $msg = &decrypt_msg($crypted_msg, $key_cipher); + $msg_hash = &transform_msg2hash($msg); + }; + if($@) { + daemon_log("process_incoming_msg: deciphering raise error", 7); + daemon_log("$@", 8); + $msg = undef; + $msg_hash = undef; + $host_name = undef; + $host_key = undef; + } else { + last; + } + } + } + + if( not defined $msg ) { + daemon_log("WARNING: bus does not understand the message:", 5); + return; + } + + # process incoming msg + my $header = @{$msg_hash->{header}}[0]; + my $source = @{$msg_hash->{source}}[0]; + + daemon_log("header from msg: $header", 1); + daemon_log("msg to process:", 5); + daemon_log($msg, 5); + + my @targets = @{$msg_hash->{target}}; + my $len_targets = @targets; + + if ($len_targets == 0){ + daemon_log("ERROR: no target specified for msg $header", 1); + + } elsif ($len_targets == 1){ + # we have only one target symbol + my $target = $targets[0]; + daemon_log("msg is for: $target", 7); + + if($target eq $bus_address) { + # msg is for bus + if($header eq 'here_i_am'){ &here_i_am($msg_hash)} + elsif($header eq 'confirm_new_passwd'){ &confirm_new_passwd($msg_hash)} + elsif($header eq 'got_ping') { &got_ping($msg_hash)} + elsif($header eq 'ping') { &ping($msg_hash)} + elsif($header eq 'who_has') { &who_has($msg_hash)} + elsif($header eq 'new_client') { &new_client($msg_hash)} + elsif($header eq 'delete_client') { &delete_client($msg_hash)} + + } elsif ($target eq "*"){ + # msg is for all server + my $query_res = $bus_known_server_db->select_dbentry( {table=>'known_server'} ); + while( my ($hit_num, $hit) = each %{ $query_res } ) { + $host_name = $hit->{hostname}; + $host_key = $hit->{hostkey}; + $msg_hash->{target} = [$host_name]; + &send_msg_hash2address($msg_hash, $host_name, $host_key); + } + return; + } + + } else { + # a list of targets is specified + my $target_address; + foreach $target_address (@targets) { + + my $query_res = $bus_known_server_db->select_dbentry( {table=>'known_server', hostname=>$target_address} ); + if( 1 == keys %{$query_res} ) { + $host_key = $query_res->{1}->{hostkey}; + &send_msg_hash2address($msg_hash, $target_address, $host_key); + next; + + } else { + $query_res = $bus_known_server_db->select_dbentry( {table=>'known_server'} ); + while( my ($hit_num, $hit) = each %{$query_res} ) { + my $host_name = $hit->{hostname}; + my $host_key = $hit->{hostkey}; + my $clients = $hit->{clients}; + my @clients = split(/,/, $clients); + foreach my $client (@clients) { + if( not $client eq $target_address ) { + next; + } + $msg_hash->{target} = [ $target_address ]; + &send_msg_hash2address($msg_hash, $host_name, $host_key); + daemon_log("bus forwards msg $header for client $target_address to server $host_name", 3); + last; + } + } + } + } + } + + return; +} + + +#=== FUNCTION ================================================================ +# NAME: get_content_of_known_daemons +# PARAMETERS: +# RETURNS: +# DESCRIPTION: +#=============================================================================== +#sub get_content_of_known_daemons { +# my ($host, $content) = @_; +# return; +#} + + +#=== FUNCTION ================================================================ +# NAME: create_passwd +# PARAMETERS: nothing +# RETURNS: new_passwd - string +# DESCRIPTION: creates a 32 bit long random passwd out of "a".."z","A".."Z",0..9 +#=============================================================================== +sub create_passwd { + my $new_passwd = ""; + for(my $i=0; $i<31; $i++) { + $new_passwd .= ("a".."z","A".."Z",0..9)[int(rand(62))] + } + return $new_passwd; +} + + +#=== FUNCTION ================================================================ +# NAME: create_ciphering +# PARAMETERS: passwd - string - used to create ciphering +# RETURNS: cipher - object +# DESCRIPTION: creates a Crypt::Rijndael::MODE_CBC object with passwd as key +#=============================================================================== +#sub create_ciphering { +# my ($passwd) = @_; +# $passwd = substr(md5_hex("$passwd") x 32, 0, 32); +# my $iv = substr(md5_hex('GONICUS GmbH'),0, 16); +# +# my $my_cipher = Crypt::Rijndael->new($passwd , Crypt::Rijndael::MODE_CBC()); +# $my_cipher->set_iv($iv); +# return $my_cipher; +#} + + +#=== FUNCTION ================================================================ +# NAME: encrypt_msg +# PARAMETERS: msg - string - message to encrypt +# my_cipher - ref - reference to a Crypt::Rijndael object +# RETURNS: crypted_msg - string - crypted message +# DESCRIPTION: crypts the incoming message with the Crypt::Rijndael module +#=============================================================================== +#sub encrypt_msg { +# my ($msg, $my_cipher) = @_; +# if(not defined $my_cipher) { print "no cipher object\n"; } +# $msg = "\0"x(16-length($msg)%16).$msg; +# my $crypted_msg = $my_cipher->encrypt($msg); +# chomp($crypted_msg = &encode_base64($crypted_msg)); +# return $crypted_msg; +#} + + +#=== FUNCTION ================================================================ +# NAME: decrypt_msg +# PARAMETERS: crypted_msg - string - message to decrypt +# my_cipher - ref - reference to a Crypt::Rijndael object +# RETURNS: msg - string - decrypted message +# DESCRIPTION: decrypts the incoming message with the Crypt::Rijndael module +#=============================================================================== +#sub decrypt_msg { +# my ($crypted_msg, $my_cipher) = @_ ; +# $crypted_msg = &decode_base64($crypted_msg); +# my $msg = $my_cipher->decrypt($crypted_msg); +# $msg =~ s/^\0*//g; +# return $msg; +#} + + +#=== FUNCTION ================================================================ +# NAME: create_xml_hash +# PARAMETERS: header - string - message header (required) +# source - string - where the message come from (required) +# target - string - where the message should go to (required) +# [header_value] - string - something usefull (optional) +# RETURNS: hash - hash - nomen est omen +# DESCRIPTION: creates a key-value hash, all values are stored in a array +#=============================================================================== +#sub create_xml_hash { +# my ($header, $source, $target, $header_value) = @_ ; +# +# if (not defined $header || not defined $source || not defined $target) { +# daemon_log("ERROR: create_xml_hash function is invoked with uncompleted parameters", 7); +# } +# +# my $hash = { +# header => [$header], +# source => [$source], +# target => [$target], +# $header => [$header_value], +# }; +# #daemon_log("create_xml_hash:", 7), +# #chomp(my $tmp = Dumper $hash); +# #daemon_log("\t$tmp\n", 7); +# return $hash +#} + + +#=== FUNCTION ================================================================ +# NAME: create_xml_string +# PARAMETERS: xml_hash - hash - hash from function create_xml_hash +# RETURNS: xml_string - string - xml string representation of the hash +# DESCRIPTION: transform the hash to a string using XML::Simple module +#=============================================================================== +#sub create_xml_string { +# my ($xml_hash) = @_ ; +# my $xml_string = $xml->XMLout($xml_hash, RootName => 'xml'); +# #$xml_string =~ s/[\n]+//g; +# return $xml_string; +#} + + +#=== FUNCTION ================================================================ +# NAME: add_content2xml_hash +# PARAMETERS: xml_ref - ref - reference to a hash from function create_xml_hash +# element - string - key for the hash +# content - string - value for the hash +# RETURNS: nothing +# DESCRIPTION: add key-value pair to xml_ref, if key alread exists, then append value to list +#=============================================================================== +#sub add_content2xml_hash { +# my ($xml_ref, $element, $content) = @_; +# if(not exists $$xml_ref{$element} ) { +# $$xml_ref{$element} = []; +# } +# my $tmp = $$xml_ref{$element}; +# push(@$tmp, $content); +# return; +#} + + +#=== FUNCTION ================================================================ +# NAME: get_content_from_xml_hash +# PARAMETERS: xml_ref - ref - reference of the xml hash +# element - string - key of the value you want +# RETURNS: value - string - if key is either header, target or source +# value - list - for all other keys in xml hash +# DESCRIPTION: +#=============================================================================== +#sub get_content_from_xml_hash { +# my ($xml_ref, $element) = @_; +# my $result = $xml_ref->{$element}; +# if( $element eq "header" || $element eq "target" || $element eq "source") { +# return @$result[0]; +# } +# return @$result; +#} + + +#=== FUNCTION ================================================================ +# NAME: open_socket +# PARAMETERS: PeerAddr - string - something like 192.168.1.1 or 192.168.1.1:10000 +# [PeerPort] - string - necessary if port not appended by PeerAddr +# RETURNS: socket - IO::Socket::INET +# DESCRIPTION: open a socket to PeerAddr +#=============================================================================== +#sub open_socket { +# my ($PeerAddr, $PeerPort) = @_ ; +# if(defined($PeerPort)){ +# $PeerAddr = $PeerAddr.":".$PeerPort; +# } +# my $socket; +# $socket = new IO::Socket::INET(PeerAddr => $PeerAddr , +# Porto => "tcp" , +# Type => SOCK_STREAM, +# Reuse => 1, +# Timeout => 5, +# ); +# if(not defined $socket) { +# return; +# } +# return $socket; +#} + + +#=== FUNCTION ================================================================ +# NAME: read_from_socket +# PARAMETERS: socket - fh - filehandel to read from +# RETURNS: result - string - readed characters from socket +# DESCRIPTION: reads data from socket in 16 byte steps +#=============================================================================== +sub read_from_socket { + my ($socket) = @_; + + $socket->blocking(1); + my $result = <$socket>; + $socket->blocking(0); + my $part_msg; + while ($part_msg = <$socket>) { + if (not defined $part_msg) { last; } + $result .= $part_msg; + } + + #my $result = ""; + #my $len = 16; + #while($len == 16){ + # my $char; + # $len = sysread($socket, $char, 16); + # if($len != 16) { last } + # if($len != 16) { last } + # $result .= $char; + #} + return $result; +} + + +#=== FUNCTION ================================================================ +# NAME: send_msg_hash2address +# PARAMETERS: msg_hash - hash - xml_hash created with function create_xml_hash +# PeerAddr string - socket address to send msg +# PeerPort string - socket port, if not included in socket address +# RETURNS: nothing +# DESCRIPTION: ???? +#=============================================================================== +#sub send_msg_hash2address { +# my ($msg_hash, $address) = @_ ; +# +# # fetch header for logging +# my $header = &get_content_from_xml_hash($msg_hash, "header"); +# +# # generate xml string +# my $msg_xml = &create_xml_string($msg_hash); +# +# # fetch the appropriated passwd from hash +# my $passwd = $known_daemons->{$address}->{passwd}; +# +# # create a ciphering object +# my $act_cipher = &create_ciphering($passwd); +# +# # encrypt xml msg +# my $crypted_msg = &encrypt_msg($msg_xml, $act_cipher); +# +# # open socket +# my $socket = &open_socket($address); +# if(not defined $socket){ +# daemon_log("ERROR: cannot send '$header'-msg to $address , server not reachable", 1); +# return; +# } +# +# # send xml msg +# print $socket $crypted_msg."\n"; +# +# close $socket; +# daemon_log("send '$header'-msg to $address", 5); +# daemon_log("crypted_msg:\n\t$crypted_msg", 7); +# return; +#} + + +#=== FUNCTION ================================================================ +# NAME: send_msg_hash2all +# PARAMETERS: msg_hash - hash - xml_hash created with function create_xml_hash +# RETURNS: nothing +# DESCRIPTION: send msg_hash to all registered daemons +#=============================================================================== +#sub send_msg_hash2all { +# my ($msg_hash) = @_; +# +# # fetch header for logging +# my $header = &get_content_from_xml_hash($msg_hash, "header"); +# +# # generate xml string +# my $msg_xml = &create_xml_string($msg_hash); +# +# # fetch a list of all target addresses +# my @targets = keys(%$known_daemons); +# +# # itterates through the list an send each the msg +# foreach my $target (@targets) { +# if($target eq $bus_address) {next}; # do not send msg to bus +# +# # fetch the appropriated passwd +# my $passwd = $known_daemons->{$target}->{passwd}; +# +# # create ciphering object +# my $act_cipher = &create_ciphering($passwd); +# +# # encrypt xml msg +# my $crypted_msg = &encrypt_msg($msg_xml, $act_cipher); +# +# # open socket +# my $socket = &open_socket($target); +# if(not defined $socket){ +# daemon_log("ERROR: cannot open socket to $target , server not reachable", 1); +# &update_known_daemons_entry(hostname=>$target, status=>"down"); +# next; +# } +# +# # send xml msg +# print $socket $crypted_msg."\n"; +# +# close $socket; +# daemon_log("send '$header'-msg to $target", 5); +# daemon_log("crypted_msg:\n\t$crypted_msg", 7); +# } +# return; +#} + + +#=== FUNCTION ================================================================ +# NAME: here_i_am +# PARAMETERS: msg_hash - hash - hash from function create_xml_hash +# RETURNS: nothing +# DESCRIPTION: process the incoming msg 'here_i_am' +#=============================================================================== +sub here_i_am { + my ($msg_hash) = @_ ; + my $source = @{$msg_hash->{source}}[0];; + + my $new_key = &create_passwd(); + + # create bus_known_server entry + my $add_hash = { + table=>"bus_known_server", + primkey=>"hostname", + hostname=>$source, + status=>"registered", + hostkey=>$bus_passwd, + clients=>"", + }; + $bus_known_server_db->add_dbentry($add_hash); + + # create outgoing msg + my $out_hash = &create_xml_hash("new_passwd", $bus_address, $source, $new_key); + &send_msg_hash2address($out_hash, $source, $bus_passwd); + + # change hostkey, reason + my $update_hash = { table=>'bus_known_server' }; + $update_hash->{where} = [ { hostname=>[$source] } ]; + $update_hash->{update} = [ { hostkey=>[$new_key] } ]; + $bus_known_server_db->update_dbentry($update_hash); + + return; +} + + +#=== FUNCTION ================================================================ +# NAME: confirm_new_passwd +# PARAMETERS: msg_hash - hash - hash from function create_xml_hash +# RETURNS: nothing +# DESCRIPTION: process this incoming message +#=============================================================================== +sub confirm_new_passwd { + my ($msg_hash) = @_ ; + my $source = @{$msg_hash->{source}}[0]; + + my $update_hash = { table=>'bus_known_server' }; + $update_hash->{where} = [ { hostname=>[$source] } ]; + $update_hash->{update} = [ { status=>['key_confirmed'] } ]; + $bus_known_server_db->update_dbentry($update_hash); + + return; +} + + +#=== FUNCTION ================================================================ +# NAME: ping +# PARAMETERS: msg_hash - hash - hash from function create_xml_hash +# RETURNS: nothing +# DESCRIPTION: process this incoming message +#=============================================================================== +sub ping { + my ($msg_hash) = @_ ; + my $header = @{$msg_hash->{header}}[0]; + my $source = @{$msg_hash->{source}}[0]; + + my $update_hash = { table=>'bus_known_server', + where=> [ { hostname=>[$source] } ], + update=> [ { status=>$header } ], + }; + $bus_known_server_db->update_dbentry($update_hash); + + my $out_hash = &create_xml_hash("got_ping", $bus_address, $source); + + my $res = $bus_known_server_db->select_dbentry( { table=>'bus_known_server', hostname=>$source } ); + my $hostkey = $res->{1}->{hostkey}; + &send_msg_hash2address($out_hash, $source, $hostkey); + + return; +} + + +#=== FUNCTION ================================================================ +# NAME: make ping +# PARAMETERS: address - string - address which should be pinged +# RETURNS: nothing +# DESCRIPTION: send ping message to address +#=============================================================================== +#sub make_ping { +# my ($address) = @_; +# daemon_log("ping:$address\n", 1); +# my $out_hash = &create_xml_hash("ping", "$bus_ip:$bus_port", $address); +# &send_msg_hash2address($out_hash, $address); +# return; +#} + + +#=== FUNCTION ================================================================ +# NAME: got_ping +# PARAMETERS: msg_hash - hash - hash from function create_xml_hash +# RETURNS: nothing +# DESCRIPTION: process this incoming message +#=============================================================================== +sub got_ping { + my ($msg_hash) = @_; + my $source = @{$msg_hash->{source}}[0]; + + my $update_hash = { table=>'bus_known_server', + where=> [ { hostname=>[$source] } ], + update=> [ { status=>'got_ping' } ], + }; + $bus_known_server_db->update_dbentry($update_hash); + + return; +} + + +#=== FUNCTION ================================================================ +# NAME: new_client +# PARAMETERS: msg_hash - hash - hash from function create_xml_hash +# RETURNS: nothing +# DESCRIPTION: process this incoming message +#=============================================================================== +sub new_client { + my ($msg_hash) = @_ ; + my $source = @{$msg_hash->{source}}[0]; + my $header = @{$msg_hash->{header}}[0]; + my $new_client = @{$msg_hash->{$header}}[0]; + + my $res = $bus_known_server_db->select_dbentry( { table=>'bus_known_server', hostname=>$source } ); + my $clients = $res->{1}->{clients}; + + # if host has alread more clients, than just append + if( length($clients) != 0 ) { + $clients .= ",$new_client"; + } else { + $clients = $new_client; + } + + my $update_hash = { table=>'bus_known_server', + where=>[ {hostname=>[$source] } ], + update=>[ {clients=>[$clients] } ], + }; + + $bus_known_server_db->update_dbentry( $update_hash ); + return; +} + + +#=== FUNCTION ================================================================ +# NAME: delete_client +# PARAMETERS: msg_hash - hash - hash from function create_xml_hash +# RETURNS: nothing +# DESCRIPTION: process this incoming message +#=============================================================================== +#sub delete_client { +# my ($msg_hash) = @_ ; +# my $source = &get_content_from_xml_hash($msg_hash, "source"); +# my $header = &get_content_from_xml_hash($msg_hash, "header"); +# my $del_client = (&get_content_from_xml_hash($msg_hash, $header))[0]; +# +# if (not exists $known_daemons->{$source}->{$del_client}) { +# daemon_log +# } +# delete $known_daemons->{$source}->{$del_client}; +# +# return; +#} + + +#=== FUNCTION ================================================================ +# NAME: print_known_daemons_hash +# PARAMETERS: nothing +# RETURNS: nothing +# DESCRIPTION: nome est omen +#=============================================================================== +#sub print_known_daemons_hash { +# my ($tmp) = @_; +# print "####################################\n"; +# print "# status of known_daemons\n"; +# my $hosts; +# my $host_hash; +# $shmkh->shlock(LOCK_EX); +# my @hosts = keys %$known_daemons; +# foreach my $host (@hosts) { +# my $status = $known_daemons->{$host}->{status} ; +# my $passwd = $known_daemons->{$host}->{passwd}; +# my $timestamp = $known_daemons->{$host}->{timestamp}; +# my @clients = keys %{$known_daemons->{$host}->{clients}}; +# my $client_string = join(", ", @clients); +# print "$host\n"; +# print "\tstatus: $status\n"; +# print "\tpasswd: $passwd\n"; +# print "\ttimestamp: $timestamp\n"; +# print "\tclients: $client_string\n"; +# +# } +# $shmkh->shunlock(LOCK_EX); +# print "####################################\n\n"; +# return; +#} + + +#=== FUNCTION ================================================================ +# NAME: create_known_daemons_entry +# PARAMETERS: hostname - string - ip address and port of host +# RETURNS: nothing +# DESCRIPTION: nome est omen +#=============================================================================== +#sub create_known_daemons_entry { +# my ($hostname) = @_; +# $shmkh->shlock(LOCK_EX); +# $known_daemons->{$hostname} = {}; +# $known_daemons->{$hostname}->{status} = "none"; +# $known_daemons->{$hostname}->{passwd} = "none"; +# $known_daemons->{$hostname}->{timestamp} = "none"; +# $known_daemons->{$hostname}->{clients} = {}; +# $shmkh->shunlock(LOCK_EX); +# return; +#} + + +#=== FUNCTION ================================================================ +# NAME: update_known_daemons_entry +# PARAMETERS: hostname - string - ip address and port of host (required) +# status - string - (optional) +# passwd - string - (optional) +# client - string - ip address and port of client (optional) +# RETURNS: nothing +# DESCRIPTION: nome est omen and updates each time the timestamp of hostname +#=============================================================================== +#sub update_known_daemons_entry { +# my $arg = { +# hostname => undef, status => undef, passwd => undef, +# client => undef, +# @_ }; +# my $hostname = $arg->{hostname}; +# my $status = $arg->{status}; +# my $passwd = $arg->{passwd}; +# my $client = $arg->{client}; +# +# if (not defined $hostname) { +# daemon_log("ERROR: function add_content2known_daemons is not invoked with requiered parameter 'hostname'", 1); +# return; +# } +# +# my ($seconds, $minutes, $hours, $monthday, $month, +# $year, $weekday, $yearday, $sommertime) = localtime(time); +# $hours = $hours < 10 ? $hours = "0".$hours : $hours; +# $minutes = $minutes < 10 ? $minutes = "0".$minutes : $minutes; +# $seconds = $seconds < 10 ? $seconds = "0".$seconds : $seconds; +# $month+=1; +# $month = $month < 10 ? $month = "0".$month : $month; +# $monthday = $monthday < 10 ? $monthday = "0".$monthday : $monthday; +# $year+=1900; +# my $t = "$year$month$monthday$hours$minutes$seconds"; +# +# $shmkh->shlock(LOCK_EX); +# if (defined $status) { +# $known_daemons->{$hostname}->{status} = $status; +# } +# if (defined $passwd) { +# $known_daemons->{$hostname}->{passwd} = $passwd; +# } +# if (defined $client) { +# $known_daemons->{$hostname}->{clients}->{$client} = ""; +# } +# $known_daemons->{$hostname}->{timestamp} = $t; +# $shmkh->shunlock(LOCK_EX); +# return; +#} + + +#==== MAIN = main ============================================================== + +# parse commandline options +Getopt::Long::Configure( "bundling" ); +GetOptions("h|help" => \&usage, + "c|config=s" => \$cfg_file, + "f|foreground" => \$foreground, + "v|verbose+" => \$verbose, + ); + +# read and set config parameters +&check_cmdline_param ; +&read_configfile; +&check_pid; + +$SIG{CHLD} = 'IGNORE'; + +# restart daemon log file +if(-e $log_file ) { unlink $log_file } +daemon_log(" ", 1); +daemon_log("$0 started!", 1); + +# forward error messages to logfile +if( ! $foreground ) { + open(STDERR, '>>', $log_file); + open(STDOUT, '>>', $log_file); +} + +# Just fork, if we"re not in foreground mode +if( ! $foreground ) { + chdir '/' or die "Can't chdir to /: $!"; + $pid = fork; + setsid or die "Can't start a new session: $!"; + umask 0; +} + +else { $pid = $$; } + +# Do something useful - put our PID into the pid_file +if( 0 != $pid ) { + open( LOCK_FILE, ">$pid_file" ); + print LOCK_FILE "$pid\n"; + close( LOCK_FILE ); + if( !$foreground ) { exit( 0 ) }; +} + +# connect to bus_known_server_db +my @server_col_names = ('hostname', 'status', 'hostkey', 'timestamp', 'clients' ); +$bus_known_server_db = GOSA::DBsqlite->new($bus_known_server_file_name); +$bus_known_server_db->create_table('bus_known_server', \@server_col_names); + + +# detect own ip and mac address +$network_interface= &get_interface_for_ip($bus_ip); +$bus_mac_address= &get_mac($network_interface); + +daemon_log("bus ip address detected: $bus_ip", 1); +daemon_log("bus mac address detected: $bus_mac_address", 1); + +# complete addresses +$bus_address = "$bus_ip:$bus_port"; + +# setup xml parser +$xml = new XML::Simple(); + +# create cipher object +$bus_cipher = &create_ciphering($bus_passwd); +$bus_address = "$bus_ip:$bus_port"; + +# create reading and writing vectors +my $rbits = my $wbits = my $ebits = ""; + +# open the bus socket +if($bus_activ eq "on") { + daemon_log(" ", 1); + $bus = IO::Socket::INET->new(LocalPort => $bus_port, + Type => SOCK_STREAM, + Reuse => 1, + Listen => 20, + ) or die "kann kein TCP-Server an Port $bus_port sein: $@\n"; + vec($rbits, fileno $bus, 1) = 1; + vec($wbits, fileno $bus, 1) = 1; + daemon_log ("start bus at $bus_ip:$bus_port", 1); +} + +# add bus to known_daemons + +#&create_known_daemons_entry($bus_address); +#&update_known_daemons_entry(hostname=>$bus_address, status=>"bus", passwd=>$bus_passwd); + + +while(1) { + my $nf = select($rbits, $wbits, undef, undef); + # error handling + if($nf < 0 ) { + } + + # something is coming in + if(vec $rbits, fileno $bus, 1 ) { + my $client = $bus->accept(); + my $other_end = getpeername($client); + if(not defined $other_end) { + daemon_log("Gegenstelle konnte nicht identifiziert werden: $!\n"); + } else { + my ($port, $iaddr) = unpack_sockaddr_in($other_end); + my $actual_ip = inet_ntoa($iaddr); + daemon_log("\naccept client from $actual_ip\n", 5); + my $in_msg = &read_from_socket($client); + if(defined $in_msg){ + &activating_child($in_msg, $actual_ip); + } else { + daemon_log("cannot read from $actual_ip\n",1); + } + } + close($client); + } + +} + + diff --git a/gosa-si-poe/gosa-si-client b/gosa-si-poe/gosa-si-client new file mode 100755 index 000000000..69a5f7ffd --- /dev/null +++ b/gosa-si-poe/gosa-si-client @@ -0,0 +1,1363 @@ +#!/usr/bin/perl +#=============================================================================== +# +# FILE: gosa-server +# +# USAGE: gosa-si-client +# +# DESCRIPTION: +# +# OPTIONS: --- +# REQUIREMENTS: libnetaddr-ip-perl +# BUGS: --- +# NOTES: +# AUTHOR: (Andreas Rettenberger), +# COMPANY: +# VERSION: 1.0 +# CREATED: 12.09.2007 08:54:41 CEST +# REVISION: --- +#=============================================================================== + +use strict; +use warnings; +use Getopt::Long; +use Config::IniFiles; +use POSIX; +use Time::HiRes qw( gettimeofday ); + +use Fcntl; +use IO::Socket::INET; +use Crypt::Rijndael; +use MIME::Base64; +use Digest::MD5 qw(md5 md5_hex md5_base64); +use XML::Simple; +use Data::Dumper; +use Sys::Syslog qw( :DEFAULT setlogsock); +use File::Spec; +use Cwd; +use NetAddr::IP; +use GOSA::GosaSupportDaemon; + + +my ($cfg_file, %cfg_defaults, $foreground, $verbose, $pid_file, $procid, $pid, $log_file); +my ($server_address, $server_ip, $server_port, $server_domain, $server_passwd, $server_cipher, $server_timeout); +my ($client_address, $client_ip, $client_port, $client_mac_address, $network_interface, $ldap_config, $pam_config, $nss_config); +my ($input_socket, $rbits, $wbits, $ebits, $xml, $known_hosts, $ldap_enabled); +my (@events); + +# default variables +my $event_dir = "/usr/lib/gosa-si/client/events"; +$known_hosts = {}; +$foreground = 0 ; +%cfg_defaults = +("general" => + {"log_file" => [\$log_file, "/var/run/".$0.".log"], + "pid_file" => [\$pid_file, "/var/run/".$0.".pid"], + }, +"client" => + {"client_port" => [\$client_port, "20083"], + "client_ip" => [\$client_ip, "0.0.0.0"], + "ldap" => [\$ldap_enabled, 1], + "ldap_config" => [\$ldap_config, "/etc/ldap/ldap.conf"], + "pam_config" => [\$pam_config, "/etc/pam_ldap.conf"], + "nss_config" => [\$nss_config, "/etc/libnss_ldap.conf"], + }, +"server" => + {"server_ip" => [\$server_ip, ""], + "server_port" => [\$server_port, "20081"], + "server_passwd" => [\$server_passwd, ""], + "server_timeout" => [\$server_timeout, 10], + "server_domain" => [\$server_domain, ""], + }, + ); + + +#=== FUNCTION ================================================================ +# NAME: read_configfile +# PARAMETERS: cfg_file - string - +# RETURNS: +# DESCRIPTION: +#=============================================================================== +sub read_configfile { + my $cfg; + if( defined( $cfg_file) && ( length($cfg_file) > 0 )) { + if( -r $cfg_file ) { + $cfg = Config::IniFiles->new( -file => $cfg_file ); + } else { + print STDERR "Couldn't read config file!"; + } + } else { + $cfg = Config::IniFiles->new() ; + } + foreach my $section (keys %cfg_defaults) { + foreach my $param (keys %{$cfg_defaults{ $section }}) { + my $pinfo = $cfg_defaults{ $section }{ $param }; + ${@$pinfo[ 0 ]} = $cfg->val( $section, $param, @$pinfo[ 1 ] ); + } + } +} + + +#=== FUNCTION ================================================================ +# NAME: logging +# PARAMETERS: level - string - default 'info' +# msg - string - +# facility - string - default 'LOG_DAEMON' +# RETURNS: +# DESCRIPTION: +#=============================================================================== +sub daemon_log { + my( $msg, $level ) = @_; + if(not defined $msg) { return } + if(not defined $level) { $level = 1 } + if(defined $log_file){ + open(LOG_HANDLE, ">>$log_file"); + if(not defined open( LOG_HANDLE, ">>$log_file" )) { + print STDERR "cannot open $log_file: $!"; + return } + chomp($msg); + if($level <= $verbose){ + print LOG_HANDLE $msg."\n"; + if(defined $foreground) { print $msg."\n" } + } + } + close( LOG_HANDLE ); +# my ($msg, $level, $facility) = @_; +# if(not defined $msg) {return} +# if(not defined $level) {$level = "info"} +# if(not defined $facility) {$facility = "LOG_DAEMON"} +# openlog($0, "pid,cons,", $facility); +# syslog($level, $msg); +# closelog; +# return; +} + + +#=== FUNCTION ================================================================ +# NAME: check_cmdline_param +# PARAMETERS: +# RETURNS: +# DESCRIPTION: +#=============================================================================== +sub check_cmdline_param () { + my $err_config; + my $err_counter = 0; + if( not defined( $cfg_file)) { + #$err_config = "please specify a config file"; + #$err_counter += 1; + my $cwd = getcwd; + my $name = "/etc/gosa-si/client.conf"; + $cfg_file = File::Spec->catfile( $cwd, $name ); + print STDERR "no conf file specified\n try to use default: $cfg_file\n"; + } + if( $err_counter > 0 ) { + &usage( "", 1 ); + if( defined( $err_config)) { print STDERR "$err_config\n"} + print STDERR "\n"; + exit( -1 ); + } +} + + +#=== FUNCTION ================================================================ +# NAME: check_pid +# PARAMETERS: +# RETURNS: +# DESCRIPTION: +#=============================================================================== +sub check_pid { + $pid = -1; + # Check, if we are already running + if( open(LOCK_FILE, "<$pid_file") ) { + $pid = ; + if( defined $pid ) { + chomp( $pid ); + if( -f "/proc/$pid/stat" ) { + my($stat) = `cat /proc/$pid/stat` =~ m/$pid \((.+)\).*/; + if( $0 eq $stat ) { + close( LOCK_FILE ); + exit -1; + } + } + } + close( LOCK_FILE ); + unlink( $pid_file ); + } + + # create a syslog msg if it is not to possible to open PID file + if (not sysopen(LOCK_FILE, $pid_file, O_WRONLY|O_CREAT|O_EXCL, 0644)) { + my($msg) = "Couldn't obtain lockfile '$pid_file' "; + if (open(LOCK_FILE, '<', $pid_file) + && ($pid = )) + { + chomp($pid); + $msg .= "(PID $pid)\n"; + } else { + $msg .= "(unable to read PID)\n"; + } + if( ! ($foreground) ) { + openlog( $0, "cons,pid", "daemon" ); + syslog( "warning", $msg ); + closelog(); + } + else { + print( STDERR " $msg " ); + } + exit( -1 ); + } +} + +#=== FUNCTION ================================================================ +# NAME: get_interface_for_ip +# PARAMETERS: ip address (i.e. 192.168.0.1) +# RETURNS: array: list of interfaces if ip=0.0.0.0, matching interface if found, undef else +# DESCRIPTION: Uses proc fs (/proc/net/dev) to get list of interfaces. +#=============================================================================== +sub get_interface_for_ip { + my $result; + my $ip= shift; + if ($ip && length($ip) > 0) { + my @ifs= &get_interfaces(); + if($ip eq "0.0.0.0") { + $result = "all"; + } else { + foreach (@ifs) { + my $if=$_; + if(get_ip($if) eq $ip) { + $result = $if; + last; + } + } + } + } + return $result; +} + +#=== FUNCTION ================================================================ +# NAME: get_interfaces +# PARAMETERS: none +# RETURNS: (list of interfaces) +# DESCRIPTION: Uses proc fs (/proc/net/dev) to get list of interfaces. +#=============================================================================== +sub get_interfaces { + my @result; + my $PROC_NET_DEV= ('/proc/net/dev'); + + open(PROC_NET_DEV, "<$PROC_NET_DEV") + or die "Could not open $PROC_NET_DEV"; + + my @ifs = ; + + close(PROC_NET_DEV); + + # Eat first two line + shift @ifs; + shift @ifs; + + chomp @ifs; + foreach my $line(@ifs) { + my $if= (split /:/, $line)[0]; + $if =~ s/^\s+//; + push @result, $if; + } + + return @result; +} + +#=== FUNCTION ================================================================ +# NAME: get_mac +# PARAMETERS: interface name (i.e. eth0) +# RETURNS: (mac address) +# DESCRIPTION: Uses ioctl to get mac address directly from system. +#=============================================================================== +sub get_mac { + my $ifreq= shift; + my $result; + if ($ifreq && length($ifreq) > 0) { + if($ifreq eq "all") { + if(defined($server_ip)) { + $result = &get_local_mac_for_remote_ip($server_ip); + } else { + $result = "00:00:00:00:00:00"; + } + } else { + my $SIOCGIFHWADDR= 0x8927; # man 2 ioctl_list + + # A configured MAC Address should always override a guessed value + if ($client_mac_address and length($client_mac_address) > 0) { + $result= $client_mac_address; + } + + socket SOCKET, PF_INET, SOCK_DGRAM, getprotobyname('ip') + or die "socket: $!"; + + if(ioctl SOCKET, $SIOCGIFHWADDR, $ifreq) { + my ($if, $mac)= unpack 'h36 H12', $ifreq; + + if (length($mac) > 0) { + $mac=~ m/^([0-9a-f][0-9a-f])([0-9a-f][0-9a-f])([0-9a-f][0-9a-f])([0-9a-f][0-9a-f])([0-9a-f][0-9a-f])([0-9a-f][0-9a-f])$/; + $mac= sprintf("%s:%s:%s:%s:%s:%s", $1, $2, $3, $4, $5, $6); + $result = $mac; + } + } + } + } + return $result; +} + +#=== FUNCTION ================================================================ +# NAME: get_ip +# PARAMETERS: interface name (i.e. eth0) +# RETURNS: (ip address) +# DESCRIPTION: Uses ioctl to get ip address directly from system. +#=============================================================================== +sub get_ip { + my $ifreq= shift; + my $result= ""; + my $SIOCGIFADDR= 0x8915; # man 2 ioctl_list + my $proto= getprotobyname('ip'); + + socket SOCKET, PF_INET, SOCK_DGRAM, $proto + or die "socket: $!"; + + if(ioctl SOCKET, $SIOCGIFADDR, $ifreq) { + my ($if, $sin) = unpack 'a16 a16', $ifreq; + my ($port, $addr) = sockaddr_in $sin; + my $ip = inet_ntoa $addr; + + if ($ip && length($ip) > 0) { + $result = $ip; + } + } + + return $result; +} + +#=== FUNCTION ================================================================ +# NAME: get_local_mac_for_remote_ip +# PARAMETERS: none (takes server_ip from global variable) +# RETURNS: (ip address from interface that is used for communication) +# DESCRIPTION: Uses ioctl to get routing table from system, checks which entry +# matches (defaultroute last). +#=============================================================================== +sub get_local_mac_for_remote_ip { + my $ifreq= shift; + my $result= "00:00:00:00:00:00"; + my $PROC_NET_ROUTE= ('/proc/net/route'); + + open(PROC_NET_ROUTE, "<$PROC_NET_ROUTE") + or die "Could not open $PROC_NET_ROUTE"; + + my @ifs = ; + + close(PROC_NET_ROUTE); + + # Eat header line + shift @ifs; + chomp @ifs; + foreach my $line(@ifs) { + my ($Iface,$Destination,$Gateway,$Flags,$RefCnt,$Use,$Metric,$Mask,$MTU,$Window,$IRTT)=split(/\s/, $line); + my $destination; + my $mask; + my ($d,$c,$b,$a)=unpack('a2 a2 a2 a2', $Destination); + $destination= sprintf("%d.%d.%d.%d", hex($a), hex($b), hex($c), hex($d)); + ($d,$c,$b,$a)=unpack('a2 a2 a2 a2', $Mask); + $mask= sprintf("%d.%d.%d.%d", hex($a), hex($b), hex($c), hex($d)); + if(new NetAddr::IP($server_ip)->within(new NetAddr::IP($destination, $mask))) { + # destination matches route, save mac and exit + $result= &get_mac($Iface); + last; + } + } + + + return $result; +} + +#=== FUNCTION ================================================================ +# NAME: usage +# PARAMETERS: +# RETURNS: +# DESCRIPTION: +#=============================================================================== +sub usage { + my( $text, $help ) = @_; + $text = undef if( "h" eq $text ); + (defined $text) && print STDERR "\n$text\n"; + if( (defined $help && $help) || (!defined $help && !defined $text) ) { + print STDERR << "EOF" ; +usage: $0 [-hvf] [-c config] + + -h : this (help) message + -c : config file + -f : foreground, process will not be forked to background + -v : be verbose (multiple to increase verbosity) +EOF + } + print "\n" ; +} + +#=== FUNCTION ================================================================ +# NAME: get_server_addresses +# PARAMETERS: +# RETURNS: +# DESCRIPTION: +#=============================================================================== +sub get_server_addresses { + my $domain= shift; + my @result; + my $dig_cmd= 'dig +nocomments srv _gosad._tcp.'.$domain; + + my $output= `$dig_cmd 2>&1`; + open (PIPE, "$dig_cmd 2>&1 |"); + while() { + chomp $_; + # If it's not a comment + if($_ =~ m/^[^;]/) { + my @matches= split /\s+/; + + # Push hostname with port + if($matches[3] eq 'SRV') { + push @result, $matches[7].':'.$matches[6]; + } elsif ($matches[3] eq 'A') { + my $i=0; + + # Substitute the hostname with the ip address of the matching A record + foreach my $host (@result) { + if ((split /\:/, $host)[0] eq $matches[0]) { + $result[$i]= $matches[4].':'.(split /\:/, $host)[1]; + } + $i++; + } + } + } + } + close(PIPE); + return @result; +} + + +#=== FUNCTION ================================================================ +# NAME: register_at_server +# PARAMETERS: +# RETURNS: +# DESCRIPTION: +#=============================================================================== +sub register_at_server { + my ($tmp) = @_; + + # create new passwd and ciphering object for client-server communication + my $new_server_passwd = &create_passwd(); + my $new_server_cipher; + + # detect all client accepted events + opendir(DIR, $event_dir) + or daemon_log("cannot find directory $event_dir!\ngosa-si-client starts without any accepting events!", 1); + my $file_name; + my @events_list = (); + while(defined($file_name = readdir(DIR))){ + if ($file_name eq "." || $file_name eq "..") { + next; + } + push(@events_list, $file_name); + } + my $events = join(",", @events_list); + daemon_log("found events: $events", 1); + + # fill in all possible servers + my @servers; + if (defined $server_domain) { + my @tmp_servers = &get_server_addresses($server_domain); + foreach my $server (@tmp_servers) { unshift(@servers, $server); } + } + # add server address from config file at first position of server list + if (defined $server_address) { + unshift(@servers, $server_address); + } + daemon_log("found servers in configuration file and via DNS:", 5); + foreach my $server (@servers) { + daemon_log("\t$server", 5); + } + + my ($rout, $wout, $reg_server); + foreach my $server (@servers) { + + # create msg hash + my $register_hash = &create_xml_hash("here_i_am", $client_address, $server); + &add_content2xml_hash($register_hash, "new_passwd", $new_server_passwd); + &add_content2xml_hash($register_hash, "mac_address", $client_mac_address); + &add_content2xml_hash($register_hash, "events", $events); + + my $tmp = print Dumper $register_hash; + + # send xml hash to server with general server passwd + my $answer = &send_msg_hash2address($register_hash, $server, $server_passwd); + + if ($answer != 0) { next; } + + # waiting for response + daemon_log("waiting for response...\n", 5); + my $nf = select($rout=$rbits, $wout=$wbits, undef, $server_timeout); + + # something is coming in + if(vec $rout, fileno $input_socket, 1) { + my $crypted_msg; + my $client = $input_socket->accept(); + my $other_end = getpeername($client); + if(not defined $other_end) { + daemon_log("client cannot be identified: $!\n"); + } else { + my ($port, $iaddr) = unpack_sockaddr_in($other_end); + my $actual_ip = inet_ntoa($iaddr); + daemon_log("\naccept client from $actual_ip\n", 5); + my $in_msg = &read_from_socket($client); + if(defined $in_msg){ + chomp($in_msg); + $crypted_msg = $in_msg; + } else { + daemon_log("cannot read from $actual_ip\n", 5); + } + } + close($client); + + # validate acknowledge msg from server + $new_server_cipher = &create_ciphering($new_server_passwd); + my $msg_hash; + eval { + my $decrypted_msg = &decrypt_msg($crypted_msg, $new_server_cipher); + daemon_log("decrypted register msg: $decrypted_msg", 5); + $msg_hash = $xml->XMLin($decrypted_msg, ForceArray=>1); + }; + if($@) { + daemon_log("ERROR: do not understand the incoming message:" , 5); + daemon_log("$@", 7); + } else { + my $header = @{$msg_hash->{header}}[0]; + if($header eq "registered") { + $reg_server = $server; + last; + } elsif($header eq "denied") { + my $reason = (&get_content_from_xml_hash($msg_hash, "denied"))[0]; + daemon_log("registration at $server denied: $reason", 1); + } else { + daemon_log("cannot register at $server", 1); + } + } + } + # if no answer arrive, try next server in list + + } + + if(defined $reg_server) { + daemon_log("registered at $reg_server", 1); + } else { + daemon_log("cannot register at any server", 1); + daemon_log("exiting!!!", 1); + exit(1); + } + + # update the global available variables + $server_address = $reg_server; + $server_passwd = $new_server_passwd; + $server_cipher = $new_server_cipher; + return; +} + + +#=== FUNCTION ================================================================ +# NAME: create_xml_hash +# PARAMETERS: +# RETURNS: +# DESCRIPTION: +#=============================================================================== +#sub create_xml_hash { +# my ($header, $source, $target, $header_value) = @_; +# my $hash = { +# header => [$header], +# source => [$source], +# target => [$target], +# $header => [$header_value], +# }; +# daemon_log("create_xml_hash:", 7), +# chomp(my $tmp = Dumper $hash); +# daemon_log("\t$tmp\n", 7); +# return $hash +#} + + +#=== FUNCTION ================================================================ +# NAME: create_xml_string +# PARAMETERS: +# RETURNS: +# DESCRIPTION: +#=============================================================================== +#sub create_xml_string { +# my ($xml_hash) = @_ ; +# my $xml_string = $xml->XMLout($xml_hash, RootName => 'xml'); +# $xml_string =~ s/[\n]+//g; +# daemon_log("create_xml_string:\n\t$xml_string\n", 7); +# return $xml_string; +#} + + +#=== FUNCTION ================================================================ +# NAME: add_content2xml_hash +# PARAMETERS: +# RETURNS: +# DESCRIPTION: +#=============================================================================== +#sub add_content2xml_hash { +# my ($xml_ref, $element, $content) = @_; +# if(not exists $$xml_ref{$element} ) { +# $$xml_ref{$element} = []; +# } +# my $tmp = $$xml_ref{$element}; +# push(@$tmp, $content); +# return; +#} + + +#=== FUNCTION ================================================================ +# NAME: get_content_from_xml_hash +# PARAMETERS: ref : reference to the xml hash +# string: key of the value you want +# RETURNS: STRING AND ARRAY +# DESCRIPTION: if key of the hash is either 'header', 'target' or 'source' the +# function returns a string cause it is expected that these keys +# do just have one value, all other keys returns an array!!! +#=============================================================================== +#sub get_content_from_xml_hash { +# my ($xml_ref, $element) = @_; +# my $result = $xml_ref->{$element}; +# if( $element eq "header" || $element eq "target" || $element eq "source") { +# return @$result[0]; +# } +# return @$result; +#} + +# my ($xml_ref, $element) = @_; +# if (exists $xml_ref->{$element}) { +# my $result = $xml_ref->{$element}; +# if( $element eq "header" || $element eq "target" || $element eq "source") { +# return @$result[0]; +# } else { +# return @$result; +# } +# +# } else { +# my $result = (); +# return @$result; +# } +#} + + +#=== FUNCTION ================================================================ +# NAME: encrypt_msg +# PARAMETERS: +# RETURNS: +# DESCRIPTION: +#=============================================================================== +#sub encrypt_msg { +# my ($msg, $my_cipher) = @_; +# if(not defined $my_cipher) { print "no cipher object\n"; } +# $msg = "\0"x(16-length($msg)%16).$msg; +# my $crypted_msg = $my_cipher->encrypt($msg); +# chomp($crypted_msg = &encode_base64($crypted_msg)); +# return $crypted_msg; +#} + + +#=== FUNCTION ================================================================ +# NAME: decrypt_msg +# PARAMETERS: +# RETURNS: +# DESCRIPTION: +#=============================================================================== +#sub decrypt_msg { +# my ($crypted_msg, $my_cipher) = @_ ; +# $crypted_msg = &decode_base64($crypted_msg); +# my $msg = $my_cipher->decrypt($crypted_msg); +# $msg =~ s/\0*//g; +# return $msg; +#} + + +#=== FUNCTION ================================================================ +# NAME: create_ciphering +# PARAMETERS: +# RETURNS: cipher object +# DESCRIPTION: +#=============================================================================== +#sub create_ciphering { +# my ($passwd) = @_; +# $passwd = substr(md5_hex("$passwd") x 32, 0, 32); +# my $iv = substr(md5_hex('GONICUS GmbH'),0, 16); +# +# #daemon_log("iv: $iv", 7); +# #daemon_log("key: $passwd", 7); +# my $my_cipher = Crypt::Rijndael->new($passwd , Crypt::Rijndael::MODE_CBC()); +# $my_cipher->set_iv($iv); +# return $my_cipher; +#} + + +#=== FUNCTION ================================================================ +# NAME: create_passwd +# PARAMETERS: +# RETURNS: cipher object +# DESCRIPTION: +#=============================================================================== +sub create_passwd { + my $new_passwd = ""; + for(my $i=0; $i<31; $i++) { + $new_passwd .= ("a".."z","A".."Z",0..9)[int(rand(62))] + } + + return $new_passwd; +} + + +#=== FUNCTION ================================================================ +# NAME: send_msg_hash2address +# PARAMETERS: msg string - xml message +# PeerAddr string - socket address to send msg +# PeerPort string - socket port, if not included in socket address +# RETURNS: nothing +# DESCRIPTION: ???? +#=============================================================================== +#sub send_msg_hash2address { +# my ($msg_hash, $address, $passwd) = @_ ; +# +# # fetch header for logging +# my $header = @{$msg_hash->{header}}[0]; +# +# # generiere xml string +# my $msg_xml = &create_xml_string($msg_hash); +# +# # hole das entsprechende passwd aus dem hash +# if(not defined $passwd) { +# if(exists $known_hosts->{$address}) { +# $passwd = $known_hosts->{$address}->{passwd}; +# } elsif ($address eq $server_address) { +# $passwd = $server_passwd; +# } else { +# daemon_log("$address not known, neither as server nor as client", 1); +# return "failed"; +# } +# } +# +# # erzeuge ein ciphering object +# my $act_cipher = &create_ciphering($passwd); +# +# # encrypt xml msg +# my $crypted_msg = &encrypt_msg($msg_xml, $act_cipher); +# +# # öffne socket +# my $socket = &open_socket($address); +# if(not defined $socket){ +# daemon_log("cannot open socket to $address, server not reachable", 1); +# daemon_log("cannot send '$header'-msg", 1); +# return "failed"; +# } +# +# # versende xml msg +# print $socket $crypted_msg."\n"; +# +# # schließe socket +# close $socket; +# +# daemon_log("send '$header'-msg to $address", 5); +# daemon_log("crypted_msg:\n\t$crypted_msg", 7); +# +# return "done"; +#} + + +#=== FUNCTION ================================================================ +# NAME: open_socket +# PARAMETERS: PeerAddr string something like 192.168.1.1 or 192.168.1.1:10000 +# [PeerPort] string necessary if port not appended by PeerAddr +# RETURNS: socket IO::Socket::INET +# DESCRIPTION: +#=============================================================================== +sub open_socket { + my ($PeerAddr, $PeerPort) = @_ ; + if(defined($PeerPort)){ + $PeerAddr = $PeerAddr.":".$PeerPort; + } + my $socket; + $socket = new IO::Socket::INET(PeerAddr => $PeerAddr , + Porto => "tcp" , + Type => SOCK_STREAM, + Timeout => 5, + ); + if(not defined $socket) { + #daemon_log("cannot connect to socket at $PeerAddr, $@\n"); + return; + } + daemon_log("open_socket:\n\t$PeerAddr", 7); + return $socket; +} + + +#=== FUNCTION ================================================================ +# NAME: read_from_socket +# PARAMETERS: socket fh - +# RETURNS: result string - readed characters from socket +# DESCRIPTION: reads data from socket in 16 byte steps +#=============================================================================== +sub read_from_socket { + my ($socket) = @_; + my $result = ""; + + $socket->blocking(1); + $result = <$socket>; + + $socket->blocking(0); + while ( my $char = <$socket> ) { + if (not defined $char) { last } + $result .= $char; + } + return $result; + + + +# my ($socket) = @_; +# my $result = ""; +# my $len = 16; +# while($len == 16){ +# my $char; +# $len = sysread($socket, $char, 16); +# if($len != 16) { last } +# if($len != 16) { last } +# $result .= $char; +# } +# return $result; +} + + +#=== FUNCTION ================================================================ +# NAME: print_known_hosts_hash +# PARAMETERS: +# RETURNS: +# DESCRIPTION: +#=============================================================================== +sub print_known_hosts_hash { + my ($tmp) = @_; + print "####################################\n"; + print "# status of known_hosts\n"; + my $hosts; + my $host_hash; + my @hosts = keys %$known_hosts; + foreach my $host (@hosts) { + #my @elements = keys %$known_hosts->{$host}; + my $status = $known_hosts->{$host}->{status} ; + my $passwd = $known_hosts->{$host}->{passwd}; + my $timestamp = $known_hosts->{$host}->{timestamp}; + print "$host\n"; + print "\t$status\n"; + print "\t$passwd\n"; + print "\t$timestamp\n"; + } + print "####################################\n"; + return; +} + +#=== FUNCTION ================================================================ +# NAME: +# PARAMETERS: +# RETURNS: +# DESCRIPTION: +#=============================================================================== +sub create_known_hosts_entry { + my ($hostname) = @_; + $known_hosts->{$hostname} = {}; + $known_hosts->{$hostname}->{status} = "none"; + $known_hosts->{$hostname}->{passwd} = "none"; + $known_hosts->{$hostname}->{timestamp} = "none"; + return; +} + + +#=== FUNCTION ================================================================ +# NAME: +# PARAMETERS: +# RETURNS: +# DESCRIPTION: +#=============================================================================== +sub update_known_hosts_entry { + my ($hostname, $status, $passwd, $timestamp) = @_; + my ($seconds, $minutes, $hours, $monthday, $month, + $year, $weekday, $yearday, $sommertime) = localtime(time); + $hours = $hours < 10 ? $hours = "0".$hours : $hours; + $minutes = $minutes < 10 ? $minutes = "0".$minutes : $minutes; + $seconds = $seconds < 10 ? $seconds = "0".$seconds : $seconds; + $month+=1; + $month = $month < 10 ? $month = "0".$month : $month; + $monthday = $monthday < 10 ? $monthday = "0".$monthday : $monthday; + $year+=1900; + my $t = "$year$month$monthday$hours$minutes$seconds"; + + if($status) { + $known_hosts->{$hostname}->{status} = $status; + } + if($passwd) { + $known_hosts->{$hostname}->{passwd} = $passwd; + } + if($timestamp) { + $t = $timestamp; + } + $known_hosts->{$hostname}->{timestamp} = $t; + return; +} + + +#=== FUNCTION ================================================================ +# NAME: +# PARAMETERS: +# RETURNS: +# DESCRIPTION: +#=============================================================================== +sub add_content2known_hosts { + my ($hostname, $element, $content) = @_; + my ($seconds, $minutes, $hours, $monthday, $month, + $year, $weekday, $yearday, $sommertime) = localtime(time); + $hours = $hours < 10 ? $hours = "0".$hours : $hours; + $minutes = $minutes < 10 ? $minutes = "0".$minutes : $minutes; + $seconds = $seconds < 10 ? $seconds = "0".$seconds : $seconds; + $month+=1; + $month = $month < 10 ? $month = "0".$month : $month; + $monthday = $monthday < 10 ? $monthday = "0".$monthday : $monthday; + $year+=1900; + my $t = "$year$month$monthday$hours$minutes$seconds"; + + $known_hosts->{$hostname}->{$element} = $content; + $known_hosts->{$hostname}->{timestamp} = $t; + return; +} + + +#=== FUNCTION ================================================================ +# NAME: +# PARAMETERS: +# RETURNS: +# DESCRIPTION: +#=============================================================================== +sub process_incoming_msg { + my ($crypted_msg) = @_; + if(not defined $crypted_msg) { + daemon_log("function 'process_incoming_msg': got no msg", 7); + } + $crypted_msg =~ /^([\s\S]*?)\.(\d{1,3}?)\.(\d{1,3}?)\.(\d{1,3}?)\.(\d{1,3}?)$/; + $crypted_msg = $1; + my $host = sprintf("%s.%s.%s.%s", $2, $3, $4, $5); + daemon_log("msg from host:", 1); + daemon_log("\t$host", 1); + daemon_log("crypted msg:", 7); + daemon_log("\t$crypted_msg", 7); + + my $act_cipher = &create_ciphering($server_passwd); + + # try to decrypt incoming msg + my ($msg, $msg_hash); + eval{ + $msg = &decrypt_msg($crypted_msg, $act_cipher); + $msg_hash = $xml->XMLin($msg, ForceArray=>1); + }; + if($@) { + daemon_log("ERROR: incoming msg cannot be decrypted with server passwd", 1); + return; + } + + my $header = @{$msg_hash->{header}}[0]; + + daemon_log("receive '$header' from $host", 1); +# daemon_log("header from msg:", 1); +# daemon_log("\t$header", 1); +# daemon_log("msg to process:", 7); +# daemon_log("\t$msg", 7); + + #check whether msg to process is a event + opendir(DIR, $event_dir) + or daemon_log("cannot find directory $event_dir, no events specified", 5); + my $file_name; + while(defined($file_name = readdir(DIR))){ + if ($file_name eq "." || $file_name eq "..") { + next; + } + if ($file_name eq $header) { + my $cmd = "$event_dir/$file_name '$msg'"; + my $result_xml = ""; + open(PIPE, "$cmd 2>&1 |"); + while() { + $result_xml.=$_; + last; + } + close(PIPE); + my $res_hash = &transform_msg2hash($result_xml); + my $res_target = @{$res_hash->{target}}[0]; + &send_msg_hash2address($res_hash, $server_address); + + return; + } + } + close(DIR); + daemon_log("could not assign the msg $header to an event", 5); + + if ($header eq 'new_ldap_config') { if ($ldap_enabled == 1) {&new_ldap_config($msg_hash)}} + elsif ($header eq 'ping') { &got_ping($msg_hash) } + elsif ($header eq 'wake_up') { &execute_event($msg_hash)} + elsif ($header eq 'new_passwd') { &new_passwd()} + else { daemon_log("ERROR: no function assigned to msg $header", 5) } + + return; +} + + +#=== FUNCTION ================================================================ +# NAME: +# PARAMETERS: +# RETURNS: +# DESCRIPTION: +#=============================================================================== +sub update_status { + my ($new_status) = @_ ; + my $out_hash = &create_xml_hash("update_status", $client_address, $server_address); + &add_content2xml_hash($out_hash, "update_status", $new_status); + &send_msg_hash2address($out_hash, $server_address); + return; +} + + +#=== FUNCTION ================================================================ +# NAME: +# PARAMETERS: +# RETURNS: +# DESCRIPTION: +#=============================================================================== +sub server_leaving { + my ($msg_hash) = @_ ; + my $source = &get_content_from_xml_hash("source"); + my $header = &get_content_from_xml_hash("header"); + + daemon_log("gosa daemon $source is going down, cause registration procedure", 1); + my $server_address = "none"; + my $server_passwd = "none"; + my $server_cipher = "none"; + + # reinitialization of default values in config file + &read_configfile; + + # registrated at new daemon + ®ister_at_server(); + + return; +} + + +sub got_ping { + my ($msg_hash) = @_ ; + + my $source = &get_content_from_xml_hash($msg_hash, 'source'); + my $target = &get_content_from_xml_hash($msg_hash, 'target'); + my $header = &get_content_from_xml_hash($msg_hash, 'header'); + + &add_content2known_hosts(hostname=>$target, status=>$header); + + my $out_hash = &create_xml_hash("got_ping", $target, $source); + &send_msg_hash2address($out_hash, $source, $server_passwd); + + return; +} + + +sub new_ldap_config { + my ($msg_hash) = @_ ; + my $element; + my @ldap_uris; + my $ldap_base; + my @ldap_options; + my @pam_options; + my @nss_options; + my $goto_admin; + my $goto_secret; + + # Transform input into array + while ( my ($key, $value) = each(%$msg_hash) ) { + if ($key =~ /^(source|target|header)$/) { + next; + } + + foreach $element (@$value) { + if ($key =~ /^ldap_uri$/) { + push (@ldap_uris, $element); + next; + } + if ($key =~ /^ldap_base$/) { + $ldap_base= $element; + next; + } + if ($key =~ /^goto_admin$/) { + $goto_admin= $element; + next; + } + if ($key =~ /^goto_secret$/) { + $goto_secret= $element; + next; + } + if ($key =~ /^ldap_cfg$/) { + push (@ldap_options, "$element"); + next; + } + if ($key =~ /^pam_cfg$/) { + push (@pam_options, "$element"); + next; + } + if ($key =~ /^nss_cfg$/) { + push (@nss_options, "$element"); + next; + } + } + } + + # Setup ldap.conf + my $file1; + my $file2; + open(file1, "> $ldap_config"); + print file1 "# This file was automatically generated by gosa-si-client. Do not change.\n"; + print file1 "URI"; + foreach $element (@ldap_uris) { + print file1 " $element"; + } + print file1 "\nBASE $ldap_base\n"; + foreach $element (@ldap_options) { + print file1 "$element\n"; + } + close (file1); + daemon_log("wrote $ldap_config", 5); + + # Setup pam_ldap.conf / libnss_ldap.conf + open(file1, "> $pam_config"); + open(file2, "> $nss_config"); + print file1 "# This file was automatically generated by gosa-si-client. Do not change.\n"; + print file2 "# This file was automatically generated by gosa-si-client. Do not change.\n"; + print file1 "uri"; + print file2 "uri"; + foreach $element (@ldap_uris) { + print file1 " $element"; + print file2 " $element"; + } + print file1 "\nbase $ldap_base\n"; + print file2 "\nbase $ldap_base\n"; + foreach $element (@pam_options) { + print file1 "$element\n"; + } + foreach $element (@nss_options) { + print file2 "$element\n"; + } + close (file2); + daemon_log("wrote $nss_config", 5); + close (file1); + daemon_log("wrote $pam_config", 5); + + # Create goto.secrets if told so + if (defined $goto_admin){ + open(file1, "> /etc/goto/secret"); + close(file1); + chown(0,0, "/etc/goto/secret"); + chmod(0600, "/etc/goto/secret"); + open(file1, "> /etc/goto/secret"); + print file1 $goto_admin.":".$goto_secret."\n"; + close(file1); + daemon_log("wrote /etc/goto/secret", 5); + } + + return; + +} + + +sub execute_event { + my ($msg_hash)= @_; + my $configdir= '/etc/gosa-si/client/events/'; + my $result; + + my $header = &get_content_from_xml_hash($msg_hash, 'header'); + my $source = &get_content_from_xml_hash($msg_hash, 'source'); + my $target = &get_content_from_xml_hash($msg_hash, 'target'); + + + if((not defined $source) + && (not defined $target) + && (not defined $header)) { + daemon_log("ERROR: Entries missing in XML msg for gosa events under $configdir"); + } else { + my $parameters=""; + my @params = &get_content_from_xml_hash($msg_hash, $header); + my $params = join(", ", @params); + daemon_log("execute_event: got parameters: $params", 5); + + if (@params) { + foreach my $param (@params) { + my $param_value = (&get_content_from_xml_hash($msg_hash, $param))[0]; + daemon_log("execute_event: parameter -> value: $param -> $param_value", 7); + $parameters.= " ".$param_value; + } + } + + my $cmd= $configdir.$header."$parameters"; + daemon_log("execute_event: executing cmd: $cmd", 7); + $result= ""; + open(PIPE, "$cmd 2>&1 |"); + while() { + $result.=$_; + } + close(PIPE); + } + + # process the event result + + + return; +} + + +sub new_passwd { + # my ($msg_hash) = @_ ; + my $new_server_passwd = &create_passwd(); + my $new_server_cipher = &create_ciphering($new_server_passwd); + + my $out_hash = &create_xml_hash("new_passwd", $client_address, $server_address, $new_server_passwd); + + &send_msg_hash2address($out_hash, $server_address, $server_passwd); + + $server_passwd = $new_server_passwd; + $server_cipher = $new_server_cipher; + return; +} + + + + +#==== MAIN = main ============================================================== + +# parse commandline options +Getopt::Long::Configure( "bundling" ); +GetOptions("h|help" => \&usage, + "c|config=s" => \$cfg_file, + "f|foreground" => \$foreground, + "v|verbose+" => \$verbose, + ); + +# read and set config parameters +&check_cmdline_param ; +&read_configfile; +&check_pid; + +if ( ! $foreground ) { + open STDIN, '/dev/null' or die "Can’t read /dev/null: $!"; + open STDOUT, '>>/dev/null' or die "Can't write to /dev/null: $!"; + open STDERR, '>>/dev/null' or die "Can't write to /dev/null: $!"; +} + + +# restart daemon log file +if(-e $log_file ) { unlink $log_file } +daemon_log(" ", 1); +daemon_log("$0 started!", 1); + +# Just fork, if we"re not in foreground mode +if( ! $foreground ) { $pid = fork(); } +else { $pid = $$; } + +# Do something useful - put our PID into the pid_file +if( 0 != $pid ) { + open( LOCK_FILE, ">$pid_file" ); + print LOCK_FILE "$pid\n"; + close( LOCK_FILE ); + if( !$foreground ) { exit( 0 ) }; +} + +# detect own ip and mac address +$network_interface= &get_interface_for_ip($client_ip); +$client_mac_address= &get_mac($network_interface); + +# ($client_ip, $client_mac_address) = &get_ip_and_mac(); +#if (not defined $client_ip) { +# die "EXIT: ip address of $0 could not be detected"; +#} +daemon_log("client ip address detected: $client_ip", 1); +daemon_log("client mac address detected: $client_mac_address", 1); + +# prepare variables +if (defined $server_ip && defined $server_port) { + $server_address = $server_ip.":".$server_port; +} +$client_address = $client_ip.":".$client_port; + +# setup xml parser +$xml = new XML::Simple(); + +# create input socket +daemon_log(" ", 1); +$rbits = $wbits = $ebits = ""; +$input_socket = IO::Socket::INET->new(LocalPort => $client_port, + Type => SOCK_STREAM, + Reuse => 1, + Listen => 20, + ); +if(not defined $input_socket){ + daemon_log("cannot be a tcp server at $client_port : $@\n"); +} else { + daemon_log("start client at $client_address",1) ; + vec($rbits, fileno $input_socket, 1) = 1; + vec($wbits, fileno $input_socket, 1) = 1; +} + +# register at server +daemon_log(" ", 1); +®ister_at_server(); + + +############## +# Debugging +############# +#sleep(2); +#&update_status("ich_bin_ein_neuer_status"); + +################################### +#everything ready, okay, lets start +################################### +while(1) { + my ($rout, $wout); + my $nf = select($rout=$rbits, $wout=$wbits, undef, undef); + + # error handling + if($nf < 0 ) { + } + + # something is coming in + if(vec $rout, fileno $input_socket, 1) { + my $client = $input_socket->accept(); + my $other_end = getpeername($client); + + if(not defined $other_end) { + daemon_log("client cannot be identified: $!"); + } else { + my ($port, $iaddr) = unpack_sockaddr_in($other_end); + my $actual_ip = inet_ntoa($iaddr); + daemon_log("accept client from $actual_ip", 5); + my $in_msg = &read_from_socket($client); + if(defined $in_msg){ + chomp($in_msg); + $in_msg = $in_msg.".".$actual_ip; + &process_incoming_msg($in_msg); + + } + } + } +} + + + + diff --git a/gosa-si-poe/gosa-si-server b/gosa-si-poe/gosa-si-server new file mode 100755 index 000000000..505cf01ba --- /dev/null +++ b/gosa-si-poe/gosa-si-server @@ -0,0 +1,1082 @@ +#!/usr/bin/perl +#=============================================================================== +# +# FILE: gosa-sd +# +# USAGE: ./gosa-sd +# +# DESCRIPTION: +# +# OPTIONS: --- +# REQUIREMENTS: libconfig-inifiles-perl libcrypt-rijndael-perl libxml-simple-perl +# libdata-dumper-simple-perl libdbd-sqlite3-perl libnet-ldap-perl +# BUGS: --- +# NOTES: +# AUTHOR: (Andreas Rettenberger), +# COMPANY: +# VERSION: 1.0 +# CREATED: 12.09.2007 08:54:41 CEST +# REVISION: --- +#=============================================================================== + + +use strict; +use warnings; +use Getopt::Long; +use Config::IniFiles; +use POSIX; +use Time::HiRes qw( gettimeofday ); + +use Fcntl; +use IO::Socket::INET; +use IO::Handle; +use IO::Select; +use Symbol qw(qualify_to_ref); +use Crypt::Rijndael; +use MIME::Base64; +use Digest::MD5 qw(md5 md5_hex md5_base64); +use XML::Simple; +use Data::Dumper; +use Sys::Syslog qw( :DEFAULT setlogsock); +use Cwd; +use File::Spec; +use GOSA::GosaSupportDaemon; +use GOSA::DBsqlite; +use POE qw(Component::Server::TCP); + +my $modules_path = "/usr/lib/gosa-si/modules"; +use lib "/usr/lib/gosa-si/modules"; + +my (%cfg_defaults, $foreground, $verbose, $ping_timeout); +my ($bus, $msg_to_bus, $bus_cipher); +my ($server, $server_mac_address, $server_events); +my ($gosa_server, $job_queue_timeout, $job_queue_table_name, $job_queue_file_name); +my ($known_modules, $known_clients_file_name, $known_server_file_name); +my ($max_clients); +my ($pid_file, $procid, $pid, $log_file); +my (%free_child, %busy_child, $child_max, $child_min, %child_alive_time, $child_timeout); +my ($arp_activ, $arp_fifo, $arp_fifo_path); + +# variables declared in config file are always set to 'our' +our (%cfg_defaults, $log_file, $pid_file, + $bus_activ, $bus_passwd, $bus_ip, $bus_port, + $server_activ, $server_ip, $server_port, $server_passwd, $max_clients, + $arp_activ, $arp_fifo_path, + $gosa_activ, $gosa_passwd, $gosa_ip, $gosa_port, $gosa_timeout, +); + +# additional variable which should be globaly accessable +our $xml; +our $server_address; +our $bus_address; +our $gosa_address; +our $no_bus; +our $no_arp; +our $verbose; +our $forground; +our $cfg_file; + +# specifies the verbosity of the daemon_log +$verbose = 0 ; + +# if foreground is not null, script will be not forked to background +$foreground = 0 ; + +# specifies the timeout seconds while checking the online status of a registrating client +$ping_timeout = 5; + +$no_bus = 0; + +$no_arp = 0; + +# name of table for storing gosa jobs +our $job_queue_table_name = 'jobs'; +our $job_db; + +# holds all other gosa-sd as well as the gosa-sd-bus +our $known_server_db; + +# holds all registrated clients +our $known_clients_db; + +%cfg_defaults = +("general" => + {"log_file" => [\$log_file, "/var/run/".$0.".log"], + "pid_file" => [\$pid_file, "/var/run/".$0.".pid"], + "child_max" => [\$child_max, 10], + "child_min" => [\$child_min, 3], + "child_timeout" => [\$child_timeout, 180], + "job_queue_timeout" => [\$job_queue_timeout, undef], + "job_queue_file_name" => [\$job_queue_file_name, '/var/lib/gosa-si/jobs.db'], + "known_clients_file_name" => [\$known_clients_file_name, '/var/lib/gosa-si/known_clients.db' ], + "known_server_file_name" => [\$known_server_file_name, '/var/lib/gosa-si/known_server.db'], + }, +"bus" => + {"bus_activ" => [\$bus_activ, "on"], + "bus_passwd" => [\$bus_passwd, ""], + "bus_ip" => [\$bus_ip, "0.0.0.0"], + "bus_port" => [\$bus_port, "20080"], + }, +"server" => + {"server_activ" => [\$server_activ, "on"], + "server_ip" => [\$server_ip, "0.0.0.0"], + "server_port" => [\$server_port, "20081"], + "server_passwd" => [\$server_passwd, ""], + "max_clients" => [\$max_clients, 100], + }, +"arp" => + {"arp_activ" => [\$arp_activ, "on"], + "arp_fifo_path" => [\$arp_fifo_path, "/var/run/gosa-si/arp-notify"], + }, +"gosa" => + {"gosa_activ" => [\$gosa_activ, "on"], + "gosa_ip" => [\$gosa_ip, "0.0.0.0"], + "gosa_port" => [\$gosa_port, "20082"], + "gosa_passwd" => [\$gosa_passwd, "none"], + }, + ); + + +#=== FUNCTION ================================================================ +# NAME: usage +# PARAMETERS: nothing +# RETURNS: nothing +# DESCRIPTION: print out usage text to STDERR +#=============================================================================== +sub usage { + print STDERR << "EOF" ; +usage: $0 [-hvf] [-c config] + + -h : this (help) message + -c : config file + -f : foreground, process will not be forked to background + -v : be verbose (multiple to increase verbosity) + -no-bus : starts $0 without connection to bus + -no-arp : starts $0 without connection to arp module + +EOF + print "\n" ; +} + + +#=== FUNCTION ================================================================ +# NAME: read_configfile +# PARAMETERS: cfg_file - string - +# RETURNS: nothing +# DESCRIPTION: read cfg_file and set variables +#=============================================================================== +sub read_configfile { + my $cfg; + if( defined( $cfg_file) && ( length($cfg_file) > 0 )) { + if( -r $cfg_file ) { + $cfg = Config::IniFiles->new( -file => $cfg_file ); + } else { + print STDERR "Couldn't read config file!\n"; + } + } else { + $cfg = Config::IniFiles->new() ; + } + foreach my $section (keys %cfg_defaults) { + foreach my $param (keys %{$cfg_defaults{ $section }}) { + my $pinfo = $cfg_defaults{ $section }{ $param }; + ${@$pinfo[ 0 ]} = $cfg->val( $section, $param, @$pinfo[ 1 ] ); + } + } +} + + +#=== FUNCTION ================================================================ +# NAME: logging +# PARAMETERS: level - string - default 'info' +# msg - string - +# facility - string - default 'LOG_DAEMON' +# RETURNS: nothing +# DESCRIPTION: function for logging +#=============================================================================== +sub daemon_log { + # log into log_file + my( $msg, $level ) = @_; + if(not defined $msg) { return } + if(not defined $level) { $level = 1 } + if(defined $log_file){ + open(LOG_HANDLE, ">>$log_file"); + if(not defined open( LOG_HANDLE, ">>$log_file" )) { + print STDERR "cannot open $log_file: $!"; + return } + chomp($msg); + if($level <= $verbose){ + my ($seconds, $minutes, $hours, $monthday, $month, + $year, $weekday, $yearday, $sommertime) = localtime(time); + $hours = $hours < 10 ? $hours = "0".$hours : $hours; + $minutes = $minutes < 10 ? $minutes = "0".$minutes : $minutes; + $seconds = $seconds < 10 ? $seconds = "0".$seconds : $seconds; + my @monthnames = ("Jan", "Feb", "Mar", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"); + $month = $monthnames[$month]; + $monthday = $monthday < 10 ? $monthday = "0".$monthday : $monthday; + $year+=1900; + my $name = $0; + $name =~ s/\.\///; + + my $log_msg = "$month $monthday $hours:$minutes:$seconds $name $msg\n"; + print LOG_HANDLE $log_msg; + if( $foreground ) { + print STDERR $log_msg; + } + } + close( LOG_HANDLE ); + } +#log into syslog +# my ($msg, $level, $facility) = @_; +# if(not defined $msg) {return} +# if(not defined $level) {$level = "info"} +# if(not defined $facility) {$facility = "LOG_DAEMON"} +# openlog($0, "pid,cons,", $facility); +# syslog($level, $msg); +# closelog; +# return; +} + + +#=== FUNCTION ================================================================ +# NAME: check_cmdline_param +# PARAMETERS: nothing +# RETURNS: nothing +# DESCRIPTION: validates commandline parameter +#=============================================================================== +sub check_cmdline_param () { + my $err_config; + my $err_counter = 0; + if(not defined($cfg_file)) { + $cfg_file = "/etc/gosa-si/server.conf"; + if(! -r $cfg_file) { + $err_config = "please specify a config file"; + $err_counter += 1; + } + } + if( $err_counter > 0 ) { + &usage( "", 1 ); + if( defined( $err_config)) { print STDERR "$err_config\n"} + print STDERR "\n"; + exit( -1 ); + } +} + + +#=== FUNCTION ================================================================ +# NAME: check_pid +# PARAMETERS: nothing +# RETURNS: nothing +# DESCRIPTION: handels pid processing +#=============================================================================== +sub check_pid { + $pid = -1; + # Check, if we are already running + if( open(LOCK_FILE, "<$pid_file") ) { + $pid = ; + if( defined $pid ) { + chomp( $pid ); + if( -f "/proc/$pid/stat" ) { + my($stat) = `cat /proc/$pid/stat` =~ m/$pid \((.+)\).*/; + if( $0 eq $stat ) { + close( LOCK_FILE ); + exit -1; + } + } + } + close( LOCK_FILE ); + unlink( $pid_file ); + } + + # create a syslog msg if it is not to possible to open PID file + if (not sysopen(LOCK_FILE, $pid_file, O_WRONLY|O_CREAT|O_EXCL, 0644)) { + my($msg) = "Couldn't obtain lockfile '$pid_file' "; + if (open(LOCK_FILE, '<', $pid_file) + && ($pid = )) + { + chomp($pid); + $msg .= "(PID $pid)\n"; + } else { + $msg .= "(unable to read PID)\n"; + } + if( ! ($foreground) ) { + openlog( $0, "cons,pid", "daemon" ); + syslog( "warning", $msg ); + closelog(); + } + else { + print( STDERR " $msg " ); + } + exit( -1 ); + } +} + +#=== FUNCTION ================================================================ +# NAME: import_modules +# PARAMETERS: module_path - string - abs. path to the directory the modules +# are stored +# RETURNS: nothing +# DESCRIPTION: each file in module_path which ends with '.pm' is imported by +# "require 'file';" +#=============================================================================== +sub import_modules { + daemon_log(" ", 1); + + if (not -e $modules_path) { + daemon_log("ERROR: cannot find directory or directory is not readable: $modules_path", 1); + } + + opendir (DIR, $modules_path) or die "ERROR while loading modules from directory $modules_path : $!\n"; + while (defined (my $file = readdir (DIR))) { + if (not $file =~ /(\S*?).pm$/) { + next; + } + eval { require $file; }; + if ($@) { + daemon_log("ERROR: gosa-si-server could not load module $file", 1); + daemon_log("$@", 5); + } else { + my $mod_name = $1; + my $info = eval($mod_name.'::get_module_info()'); + my ($input_address, $input_key, $input, $input_active, $input_type) = @{$info}; + $known_modules->{$mod_name} = $info; + + daemon_log("module $mod_name loaded", 1); + } + } + + # for debugging + #while ( my ($module, $tag_hash) = each(%$known_modules)) { + # print "\tmodule: $module"."\n"; + # print "\ttags: ".join(", ", keys(%$tag_hash))."\n"; + #} + close (DIR); +} + + +#=== FUNCTION ================================================================ +# NAME: sig_int_handler +# PARAMETERS: signal - string - signal arose from system +# RETURNS: noting +# DESCRIPTION: handels tasks to be done befor signal becomes active +#=============================================================================== +sub sig_int_handler { + my ($signal) = @_; + + daemon_log("shutting down gosa-si-server", 1); + exit(1); +} +$SIG{INT} = \&sig_int_handler; + + +#=== FUNCTION ================================================================ +# NAME: activating_child +# PARAMETERS: msg - string - incoming message +# host - string - host from which the incomming message comes +# RETURNS: nothing +# DESCRIPTION: handels the distribution of incoming messages to working childs +#=============================================================================== +sub activating_child { + my ($msg, $host, $client) = @_; + my $child = &get_processing_child(); + if(!$child) { + daemon_log("Got no child! Trying again", 5); + sleep(1); + $child = &get_processing_child(); + if(!$child) { + daemon_log("Got no child at the second try! Trying again", 5); + } + } + my $pipe_wr = $$child{'pipe_wr'}; + my $pipe_rd = $$child{'pipe_rd'}; + $$child{client_ref} = $client; + + daemon_log("activating: childpid:$$child{'pid'}", 5); + + print $pipe_wr $msg.".".$host."\n"; + + return; +} + + +#=== FUNCTION ================================================================ +# NAME: get_processing_child +# PARAMETERS: nothing +# RETURNS: child - hash - holding the process id and the references to the pipe +# handles pipe_wr and pipe_rd +# DESCRIPTION: handels the forking, reactivating and keeping alive tasks +#=============================================================================== +sub get_processing_child { + my $child; + + while(my ($key, $val) = each(%free_child)) { + my $exitus_pid = waitpid($key, WNOHANG); + if($exitus_pid != 0) { + delete $free_child{$key}; + } + daemon_log("free child:$key", 5); + } + # check @free_child and @busy_child + my $free_len = scalar(keys(%free_child)); + my $busy_len = scalar(keys(%busy_child)); + daemon_log("free children $free_len, busy children $busy_len", 5); + + # if there is a free child, let the child work + if($free_len > 0){ + my @keys = keys(%free_child); + $child = $free_child{$keys[0]}; + if(defined $child) { + $busy_child{$$child{'pid'}} = $child ; + delete $free_child{$$child{'pid'}}; + } + return $child; + } + + # no free child, try to fork another one + if($free_len + $busy_len < $child_max) { + + daemon_log("not enough children, create a new one", 5); + + # New pipes for communication + my( $PARENT_wr, $PARENT_rd ); + my( $CHILD_wr, $CHILD_rd ); + pipe( $CHILD_rd, $PARENT_wr ); + pipe( $PARENT_rd, $CHILD_wr ); + $PARENT_wr->autoflush(1); + $CHILD_wr->autoflush(1); + + ############ + # fork child + ############ + my $child_pid = fork(); + + #CHILD + if($child_pid == 0) { + # Close unused pipes + close( $CHILD_rd ); + close( $CHILD_wr ); + while( 1 ) { + my $rbits = ""; + vec( $rbits, fileno $PARENT_rd , 1 ) = 1; + my $nf = select($rbits, undef, undef, $child_timeout); + if($nf < 0 ) { + die "select(): $!\n"; + } elsif (! $nf) { + # if already child_min childs are alive, then leave loop + $free_len = scalar(keys(%free_child)); + $busy_len = scalar(keys(%busy_child)); + if($free_len + $busy_len >= $child_min) { + last; + } else { + redo; + } + } + + # a job for a child arise + if ( vec $rbits, fileno $PARENT_rd, 1 ) { + # read everything from pipe + my $msg = ""; + $PARENT_rd->blocking(0); + while(1) { + my $read = <$PARENT_rd>; + if(not defined $read) { last} + $msg .= $read; + } + + ###################################### + # forward msg to all imported modules + no strict "refs"; + my $answer; + my %act_modules = %$known_modules; + while( my ($module, $info) = each(%act_modules)) { + my $tmp = &{ $module."::process_incoming_msg" }($msg); + if (defined $tmp) { + $answer = $tmp; + } + } + daemon_log("processing of msg finished", 5); + + if (defined $answer) { + print $PARENT_wr $answer."\n"; + print $PARENT_wr "ENDMESSAGE\n"; + my $len_answer = length $answer; + daemon_log("with answer: length of answer: $len_answer", 7); + daemon_log("\n$answer", 7); + } else { + print $PARENT_wr "done"."\n"; + print $PARENT_wr "ENDMESSAGE\n"; + } + redo; + } + } + # childs leaving the loop are allowed to die + exit(0); + + + #PARENT + } else { + # Close unused pipes + close( $PARENT_rd ); + close( $PARENT_wr ); + + # add child to child alive hash + my %child_hash = ( + 'pid' => $child_pid, + 'pipe_wr' => $CHILD_wr, + 'pipe_rd' => $CHILD_rd, + 'client_ref' => "", + ); + + $child = \%child_hash; + $busy_child{$$child{'pid'}} = $child; + return $child; + } + } +} + + +#=== FUNCTION ================================================================ +# NAME: read_from_socket +# PARAMETERS: socket fh - +# RETURNS: result string - readed characters from socket +# DESCRIPTION: reads data from socket in 16 byte steps +#=============================================================================== +sub read_from_socket { + my ($socket) = @_; + my $result = ""; + + $socket->blocking(1); + $result = <$socket>; + + $socket->blocking(0); + while ( my $char = <$socket> ) { + if (not defined $char) { last } + $result .= $char; + } + + return $result; +} + + +#=== FUNCTION ================================================================ +# NAME: print_known_daemons +# PARAMETERS: nothing +# RETURNS: nothing +# DESCRIPTION: nomen est omen +#=============================================================================== +#sub print_known_daemons { +# my ($tmp) = @_ ; +# print "####################################\n"; +# print "# status of known_daemons\n"; +# $shmda->shlock(LOCK_EX); +# my @hosts = keys %$known_daemons; +# foreach my $host (@hosts) { +# my $status = $known_daemons->{$host}->{status} ; +# my $passwd = $known_daemons->{$host}->{passwd}; +# my $timestamp = $known_daemons->{$host}->{timestamp}; +# print "$host\n"; +# print "\tstatus: $status\n"; +# print "\tpasswd: $passwd\n"; +# print "\ttimestamp: $timestamp\n"; +# } +# $shmda->shunlock(LOCK_EX); +# print "####################################\n"; +# return; +#} + + +#=== FUNCTION ================================================================ +# NAME: create_known_daemon +# PARAMETERS: hostname - string - key for the hash known_daemons +# RETURNS: nothing +# DESCRIPTION: creates a dummy entry for hostname in known_daemons +#=============================================================================== +#sub create_known_daemon { +# my ($hostname) = @_; +# $shmda->shlock(LOCK_EX); +# $known_daemons->{$hostname} = {}; +# $known_daemons->{$hostname}->{status} = "none"; +# $known_daemons->{$hostname}->{passwd} = "none"; +# $known_daemons->{$hostname}->{timestamp} = "none"; +# $shmda->shunlock(LOCK_EX); +# return; +#} + + +#=== FUNCTION ================================================================ +# NAME: add_content2known_daemons +# PARAMETERS: hostname - string - ip address and port of host (required) +# status - string - (optional) +# passwd - string - (optional) +# mac_address - string - mac address of host (optional) +# RETURNS: nothing +# DESCRIPTION: nome est omen and updates each time the timestamp of hostname +#=============================================================================== +#sub add_content2known_daemons { +# my $arg = { +# hostname => undef, status => undef, passwd => undef, +# mac_address => undef, events => undef, +# @_ }; +# my $hostname = $arg->{hostname}; +# my $status = $arg->{status}; +# my $passwd = $arg->{passwd}; +# my $mac_address = $arg->{mac_address}; +# my $events = $arg->{events}; +# +# if (not defined $hostname) { +# daemon_log("ERROR: function add_content2known_daemons is not invoked with requiered parameter 'hostname'", 1); +# return; +# } +# +# my ($seconds, $minutes, $hours, $monthday, $month, +# $year, $weekday, $yearday, $sommertime) = localtime(time); +# $hours = $hours < 10 ? $hours = "0".$hours : $hours; +# $minutes = $minutes < 10 ? $minutes = "0".$minutes : $minutes; +# $seconds = $seconds < 10 ? $seconds = "0".$seconds : $seconds; +# $month+=1; +# $month = $month < 10 ? $month = "0".$month : $month; +# $monthday = $monthday < 10 ? $monthday = "0".$monthday : $monthday; +# $year+=1900; +# my $t = "$year$month$monthday$hours$minutes$seconds"; +# +# $shmda->shlock(LOCK_EX); +# if (defined $status) { +# $known_daemons->{$hostname}->{status} = $status; +# } +# if (defined $passwd) { +# $known_daemons->{$hostname}->{passwd} = $passwd; +# } +# if (defined $mac_address) { +# $known_daemons->{$hostname}->{mac_address} = $mac_address; +# } +# if (defined $events) { +# $known_daemons->{$hostname}->{events} = $events; +# } +# $known_daemons->{$hostname}->{timestamp} = $t; +# $shmda->shlock(LOCK_EX); +# return; +#} + + +#=== FUNCTION ================================================================ +# NAME: update_known_daemons +# PARAMETERS: hostname - string - ip address and port of host (required) +# status - string - (optional) +# passwd - string - (optional) +# client - string - ip address and port of client (optional) +# RETURNS: nothing +# DESCRIPTION: nome est omen and updates each time the timestamp of hostname +#=============================================================================== +#sub update_known_daemons { +# my $arg = { +# hostname => undef, status => undef, passwd => undef, +# @_ }; +# my $hostname = $arg->{hostname}; +# my $status = $arg->{status}; +# my $passwd = $arg->{passwd}; +# +# if (not defined $hostname) { +# daemon_log("ERROR: function add_content2known_daemons is not invoked with requiered parameter 'hostname'", 1); +# return; +# } +# +# my ($seconds, $minutes, $hours, $monthday, $month, +# $year, $weekday, $yearday, $sommertime) = localtime(time); +# $hours = $hours < 10 ? $hours = "0".$hours : $hours; +# $minutes = $minutes < 10 ? $minutes = "0".$minutes : $minutes; +# $seconds = $seconds < 10 ? $seconds = "0".$seconds : $seconds; +# $month+=1; +# $month = $month < 10 ? $month = "0".$month : $month; +# $monthday = $monthday < 10 ? $monthday = "0".$monthday : $monthday; +# $year+=1900; +# my $t = "$year$month$monthday$hours$minutes$seconds"; +# +# $shmda->shlock(LOCK_EX); +# if (defined $status) { +# $known_daemons->{$hostname}->{status} = $status; +# } +# if (defined $passwd) { +# $known_daemons->{$hostname}->{passwd} = $passwd; +# } +# $known_daemons->{$hostname}->{timestamp} = $t; +# $shmda->shunlock(LOCK_EX); +# return; +#} + + +#=== FUNCTION ================================================================ +# NAME: print_known_clients +# PARAMETERS: nothing +# RETURNS: nothing +# DESCRIPTION: nomen est omen +#=============================================================================== +#sub print_known_clients { +# +# print "####################################\n"; +# print "# status of known_clients\n"; +# $shmcl->shlock(LOCK_EX); +# my @hosts = keys %$known_clients; +# if (@hosts) { +# foreach my $host (@hosts) { +# my $status = $known_clients->{$host}->{status} ; +# my $passwd = $known_clients->{$host}->{passwd}; +# my $timestamp = $known_clients->{$host}->{timestamp}; +# my $mac_address = $known_clients->{$host}->{mac_address}; +# my $events = $known_clients->{$host}->{events}; +# print "$host\n"; +# print "\tstatus: $status\n"; +# print "\tpasswd: $passwd\n"; +# print "\ttimestamp: $timestamp\n"; +# print "\tmac_address: $mac_address\n"; +# print "\tevents: $events\n"; +# } +# } +# $shmcl->shunlock(LOCK_EX); +# print "####################################\n"; +# return; +#} + + +#=== FUNCTION ================================================================ +# NAME: create_known_client +# PARAMETERS: hostname - string - key for the hash known_clients +# RETURNS: nothing +# DESCRIPTION: creates a dummy entry for hostname in known_clients +#=============================================================================== +sub create_known_client { + my ($hostname) = @_; + + my $entry = { table=>'known_clients', + hostname=>$hostname, + status=>'none', + hostkey=>'none', + timestamp=>'none', + macaddress=>'none', + events=>'none', + }; + my $res = $known_clients_db->add_dbentry($entry); + if ($res > 0) { + daemon_log("ERROR: cannot add entry to known_clients.db: $res", 1); + } + + return; +} + +#==== MAIN = main ============================================================== + +# parse commandline options +Getopt::Long::Configure( "bundling" ); +GetOptions("h|help" => \&usage, + "c|config=s" => \$cfg_file, + "f|foreground" => \$foreground, + "v|verbose+" => \$verbose, + "no-bus+" => \$no_bus, + "no-arp+" => \$no_arp, + ); + +# read and set config parameters +&check_cmdline_param ; +&read_configfile; +&check_pid; + + +$SIG{CHLD} = 'IGNORE'; + +# forward error messages to logfile +if( ! $foreground ) { + open(STDERR, '>>', $log_file); + open(STDOUT, '>>', $log_file); +} + +# Just fork, if we are not in foreground mode +if( ! $foreground ) { + chdir '/' or die "Can't chdir to /: $!"; + $pid = fork; + setsid or die "Can't start a new session: $!"; + umask 0; +} else { + $pid = $$; +} + +# Do something useful - put our PID into the pid_file +if( 0 != $pid ) { + open( LOCK_FILE, ">$pid_file" ); + print LOCK_FILE "$pid\n"; + close( LOCK_FILE ); + if( !$foreground ) { + exit( 0 ) + }; +} + +daemon_log(" ", 1); +daemon_log("$0 started!", 1); + +# delete old DBsqlite lock files +system('rm -f /tmp/gosa_si_lock*'); + +# connect to gosa-si job queue +my @job_col_names = ("id", "timestamp", "status", "result", "headertag", "targettag", "xmlmessage", "macaddress"); +$job_db = GOSA::DBsqlite->new($job_queue_file_name); +$job_db->create_table('jobs', \@job_col_names); + +# connect to known_clients_db +my @clients_col_names = ('hostname', 'status', 'hostkey', 'timestamp', 'macaddress', 'events'); +$known_clients_db = GOSA::DBsqlite->new($known_clients_file_name); +$known_clients_db->create_table('known_clients', \@clients_col_names); + +# connect to known_server_db +my @server_col_names = ('hostname', 'status', 'hostkey', 'timestamp'); +$known_server_db = GOSA::DBsqlite->new($known_server_file_name); +$known_server_db->create_table('known_server', \@server_col_names); + +# import all modules +&import_modules; + +# check wether all modules are gosa-si valid passwd check + +# create reading and writing vectors +my $rbits = my $wbits = my $ebits = ""; + +# add all module inputs to listening vector +# while( my ($mod_name, $info) = each %$known_modules ) { +# my ($input_address, $input_key, $input, $input_activ, $input_type) = @{$info}; +# vec($rbits, fileno $input, 1) = 1; +# +# } + + +## start arp fifo +#if ($no_arp > 0) { +# $arp_activ = "off"; +#} +#my $my_fifo; +#if($arp_activ eq "on") { +# daemon_log(" ", 1); +# $my_fifo = &open_fifo($arp_fifo_path); +# if($my_fifo == 0) { die "fifo file disappeared\n" } +# sysopen($arp_fifo, $arp_fifo_path, O_RDWR) or die "can't read from $arp_fifo: $!" ; +# +# vec($rbits, fileno $arp_fifo, 1) = 1; +#} +# + +################################### +##everything ready, okay, lets start +################################### +#while(1) { +# +# # add all handles from the childs +# while ( my ($pid, $child_hash) = each %busy_child ) { +# +# # check whether process still exists +# my $exitus_pid = waitpid($pid, WNOHANG); +# if($exitus_pid != 0) { +# delete $busy_child{$pid}; +# next; +# } +# +# # add child fhd to the listener +# my $fhd = $$child_hash{'pipe_rd'}; +# vec($rbits, fileno $fhd, 1) = 1; +# } +# +# my ($rout, $wout); +# my $nf = select($rout=$rbits, $wout=$wbits, undef, $job_queue_timeout); +# +# # error handling +# if($nf < 0 ) { +# } +# +# +## if($arp_activ eq "on" && vec($rout, fileno $arp_fifo, 1)) { +## my $in_msg = <$arp_fifo>; +## chomp($in_msg); +## print "arp_activ: msg: $in_msg\n"; +## my $act_passwd = $known_daemons->{$bus_address}->{passwd}; +## print "arp_activ: arp_passwd: $act_passwd\n"; +## +## my $in_msg_hash = $xml->XMLin($in_msg, ForceArray=>1); +## +## my $target = &get_content_from_xml_hash($in_msg_hash, 'target'); +## +## if ($target eq $server_address) { +## print "arp_activ: forward to server\n"; +## my $arp_cipher = &create_ciphering($act_passwd); +## my $crypted_msg = &encrypt_msg($in_msg, $arp_cipher); +## &activating_child($crypted_msg, $server_ip); +## } else { +## print "arp_activ: send to bus\n"; +## &send_msg_hash2address($in_msg_hash, $bus_address); +## } +## print "\n"; +## } +# +# +# # check input fhd of all modules +# while ( my ($mod_name, $info) = each %$known_modules) { +# my $input_fhd = @{$info}[2]; +# my $input_activ = @{$info}[3]; +# if (vec($rout, fileno $input_fhd, 1) && $input_activ eq 'on') { +# daemon_log(" ", 1); +# my $client = $input_fhd->accept(); +# my $other_end = getpeername($client); +# if(not defined $other_end) { +# daemon_log("client cannot be identified: $!"); +# } else { +# my ($port, $iaddr) = unpack_sockaddr_in($other_end); +# my $actual_ip = inet_ntoa($iaddr); +# daemon_log("accept client at daemon socket from $actual_ip", 5); +# my $in_msg = &read_from_socket($client); +# if(defined $in_msg){ +# chomp($in_msg); +# &activating_child($in_msg, $actual_ip, $client); +# } else { +# daemon_log("cannot read from $actual_ip", 5); +# } +# } +# } +# } +# +# # check all processing childs whether they are finished ('done') or +# while ( my ($pid, $child_hash) = each %busy_child ) { +# my $fhd = $$child_hash{'pipe_rd'}; +# +# if (vec($rout, fileno $fhd, 1) ) { +# daemon_log("process child $pid is ready to read", 5); +# +# my $in_msg; +# while (1) { +# my $part_in_msg = <$fhd>; +# if( $part_in_msg eq "ENDMESSAGE\n") { +# last; +# } +# $in_msg .= $part_in_msg; +# } +# chomp($in_msg); +# +# if (not defined $in_msg) { +# next; +# } elsif ($in_msg =~ "done") { +# daemon_log("process child read: $in_msg", 7); +# delete $busy_child{$pid}; +# $free_child{$pid} = $child_hash; +# +# } else { +# daemon_log("process child read:", 7); +# daemon_log("\n$in_msg", 8); +# # send computed answer back to connected client +# my $act_client = $busy_child{$pid}{client_ref}; +# print $act_client $in_msg."\n"; +# delete $busy_child{$pid}; +# $free_child{$pid} = $child_hash; +# +# } +# } +# } +# +# # check gosa job queue for jobs with executable timestamp +# my ($seconds, $minutes, $hours, $monthday, $month, +# $year, $weekday, $yearday, $sommertime) = localtime(time); +# $hours = $hours < 10 ? $hours = "0".$hours : $hours; +# $minutes = $minutes < 10 ? $minutes = "0".$minutes : $minutes; +# $seconds = $seconds < 10 ? $seconds = "0".$seconds : $seconds; +# $month+=1; +# $month = $month < 10 ? $month = "0".$month : $month; +# $monthday = $monthday < 10 ? $monthday = "0".$monthday : $monthday; +# $year+=1900; +# my $timestamp = "$year$month$monthday$hours$minutes$seconds"; +# +# +# my $res = $job_db->select_dbentry( { table=>$job_queue_table_name, status=>'waiting', timestamp=>$timestamp } ); +# +# while( my ($id, $hit) = each %{$res} ) { +# +# my $jobdb_id = $hit->{id}; +# my $macaddress = $hit->{macaddress}; +# my $job_msg_hash = &transform_msg2hash($hit->{xmlmessage}); +# my $out_msg_hash = $job_msg_hash; +# my $res_hash = $known_clients_db->select_dbentry( {table=>'known_clients', macaddress=>$macaddress} ); +# # expect macaddress is unique!!!!!! +# my $target = $res_hash->{1}->{hostname}; +# +# if (not defined $target) { +# &daemon_log("ERROR: no host found for mac address: $job_msg_hash->{mac}[0]", 1); +# &daemon_log("xml message: $hit->{xmlmessage}", 5); +# my $update_hash = { table=>$job_queue_table_name, +# update=> [ { status=>['error'], result=>["no host found for mac address"] } ], +# where=> [ { id=>[$jobdb_id] } ], +# }; +# my $res = $job_db->update_dbentry($update_hash); +# +# next; +# } +# +# # add target +# &add_content2xml_hash($out_msg_hash, "target", $target); +# +# # add new header +# my $out_header = $job_msg_hash->{header}[0]; +# $out_header =~ s/job_/gosa_/; +# delete $out_msg_hash->{header}; +# &add_content2xml_hash($out_msg_hash, "header", $out_header); +# +# # add sqlite_id +# &add_content2xml_hash($out_msg_hash, "jobdb_id", $jobdb_id); +# +# my $out_msg = &create_xml_string($out_msg_hash); +# +# # encrypt msg as a GosaPackage module +# my $cipher = &create_ciphering($gosa_passwd); +# my $crypted_out_msg = &encrypt_msg($out_msg, $cipher); +# +# my $error = &send_msg_hash2address($out_msg_hash, "$gosa_ip:$gosa_port", $gosa_passwd); +# +# if ($error == 0) { +# my $sql = "UPDATE '$job_queue_table_name' SET status='processing', targettag='$target' WHERE id='$jobdb_id'"; +# my $res = $job_db->exec_statement($sql); +# } else { +# my $update_hash = { table=>$job_queue_table_name, +# update=> [ { status=>'error' } ], +# where=> [ { id=>$jobdb_id } ], +# }; +# my $res = $job_db->update_dbentry($update_hash); +# } +# +# } +# +# +#} +print STDERR "Server Port: $server_port\n"; +POE::Component::Server::TCP->new +( + Port => $server_port, + ClientInput => \&client_input, +); + +POE::Kernel->run(); +exit; + +sub client_input { + my ($heap,$input,$wheel) = @_[HEAP, ARG0, ARG1]; + #print STDERR Dumper($heap); + ###################################### + # forward msg to all imported modules + no strict "refs"; + my $answer; + my %act_modules = %$known_modules; + while( my ($module, $info) = each(%act_modules)) { + daemon_log("Processing module ".$module, 3); + my $tmp = &{ $module."::process_incoming_msg" }($input.".".$heap->{remote_ip}."\n"); + if (defined $tmp) { + $answer = $tmp; + } + daemon_log("Got answer from module ".$module.": ".$answer,3); + } + daemon_log("processing of msg finished", 5); + + if (defined $answer) { + $heap->{client}->put($answer); + } else { + $heap->{client}->put("done\n"); + } + #redo; +} diff --git a/gosa-si-poe/modules/DBsqlite.pm b/gosa-si-poe/modules/DBsqlite.pm new file mode 100644 index 000000000..20463d782 --- /dev/null +++ b/gosa-si-poe/modules/DBsqlite.pm @@ -0,0 +1,377 @@ +package GOSA::DBsqlite; + + +use strict; +use warnings; +use DBI; +use Data::Dumper; +use threads; +use Time::HiRes qw(usleep); + +my $col_names = {}; + +sub new { + my $class = shift; + my $db_name = shift; + + my $lock='/tmp/gosa_si_lock'; + my $_lock = $db_name; + $_lock =~ tr/\//_/; + $lock.=$_lock; + my $self = {dbh=>undef,db_name=>undef,db_lock=>undef,db_lock_handle=>undef}; + my $dbh = DBI->connect("dbi:SQLite:dbname=$db_name"); + $self->{dbh} = $dbh; + $self->{db_name} = $db_name; + $self->{db_lock} = $lock; + bless($self,$class); + + return($self); +} + +sub lock_exists : locked { + my $self=shift; + my $funcname=shift; + my $lock = $self->{db_lock}; + my $result=(-f $lock); + if($result) { + #print STDERR "(".((defined $funcname)?$funcname:"").") Lock (PID ".$$.") $lock gefunden\n"; + usleep 100; + } + return $result; +} + +sub create_lock : locked { + my $self=shift; + my $funcname=shift; + #print STDERR "(".((defined $funcname)?$funcname:"").") Erzeuge Lock (PID ".$$.") ".($self->{db_lock})."\n"; + + my $lock = $self->{db_lock}; + while( -f $lock ) { + #print STDERR "(".((defined $funcname)?$funcname:"").") Lock (PID ".$$.") $lock gefunden\n"; + sleep 1; + } + + open($self->{db_lock_handle},'>',$self->{db_lock}); +} + +sub remove_lock : locked { + my $self=shift; + my $funcname=shift; + #print STDERR "(".((defined $funcname)?$funcname:"").") Entferne Lock (PID ".$$.") ".$self->{db_lock}."\n"; + close($self->{db_lock_handle}); + unlink($self->{db_lock}); +} + +sub create_table { + my $self = shift; + my $table_name = shift; + my $col_names_ref = shift; + $col_names->{ $table_name } = $col_names_ref; + my $col_names_string = join(', ', @{$col_names_ref}); + my $sql_statement = "CREATE TABLE IF NOT EXISTS $table_name ( $col_names_string )"; + &create_lock($self,'create_table'); + $self->{dbh}->do($sql_statement); + &remove_lock($self,'create_table'); + return 0; +} + + + +sub add_dbentry { + my $self = shift; + my $arg = shift; + + # if dbh not specified, return errorflag 1 + my $table = $arg->{table}; + if( not defined $table ) { + return 1 ; + } + + # specify primary key in table + if (not exists $arg->{primkey}) { + return 2; + } + my $primkey = $arg->{primkey}; + + # if primkey is id, fetch max id from table and give new job id= max(id)+1 + if ($primkey eq 'id') { + my $id; + my $sql_statement = "SELECT MAX(id) FROM $table"; + &create_lock($self,'add_dbentry'); + my $max_id = @{ @{ $self->{dbh}->selectall_arrayref($sql_statement) }[0] }[0]; + &remove_lock($self,'add_dbentry'); + if( defined $max_id) { + $id = $max_id + 1; + } else { + $id = 1; + } + $arg->{id} = $id; + } + + # check wether value to primary key is specified + if ( not exists $arg->{ $primkey } ) { + return 3; + } + + # if timestamp is not provided, add timestamp + if( not exists $arg->{timestamp} ) { + $arg->{timestamp} = &get_time; + } + + # check wether primkey is unique in table, otherwise return errorflag + my $sql_statement = "SELECT * FROM $table WHERE $primkey='$arg->{$primkey}'"; + &create_lock($self,'add_dbentry'); + my $res = @{ $self->{dbh}->selectall_arrayref($sql_statement) }; + &remove_lock($self,'add_dbentry'); + if ($res == 0) { + # fetch column names of table + my $col_names = &get_table_columns("",$table); + + # assign values to column name variables + my @add_list; + foreach my $col_name (@{$col_names}) { + # use function parameter for column values + if (exists $arg->{$col_name}) { + push(@add_list, $arg->{$col_name}); + } + } + + my $sql_statement = "INSERT INTO $table VALUES ('".join("', '", @add_list)."')"; + print STDERR $sql_statement; + &create_lock($self,'add_dbentry'); + my $db_res = $self->{dbh}->do($sql_statement); + &remove_lock($self,'add_dbentry'); + if( $db_res != 1 ) { + return 4; + } else { + return 0; + } + + } else { + my $update_hash = { table=>$table }; + $update_hash->{where} = [ { $primkey=>[ $arg->{$primkey} ] } ]; + $update_hash->{update} = [ {} ]; + while( my ($pram, $val) = each %{$arg} ) { + if( $pram eq 'table' ) { next; } + if( $pram eq 'primkey' ) { next; } + $update_hash->{update}[0]->{$pram} = [$val]; + } + my $db_res = &update_dbentry( $self, $update_hash ); + if( $db_res != 1 ) { + return 5; + } else { + return 0; + } + + } +} + + +# error-flags +# 1 no table ($table) defined +# 2 no restriction parameter ($restric_pram) defined +# 3 no restriction value ($restric_val) defined +# 4 column name not known in table +# 5 no column names to change specified +sub update_dbentry { + my $self = shift; + my $arg = shift; + + + # check completeness of function parameter + # extract table statement from arg hash + my $table = $arg->{table}; + if (not defined $table) { + return 1; + } else { + delete $arg->{table}; + } + + # extract where parameter from arg hash + my $where_statement = ""; + if( exists $arg->{where} ) { + my $where_hash = @{ $arg->{where} }[0]; + if( 0 < keys %{ $where_hash } ) { + my @where_list; + while( my ($rest_pram, $rest_val) = each %{ $where_hash } ) { + my $statement; + if( $rest_pram eq 'timestamp' ) { + $statement = "$rest_pram<'@{ $rest_val }[0]'"; + } else { + $statement = "$rest_pram='@{ $rest_val }[0]'"; + } + push( @where_list, $statement ); + } + $where_statement .= "WHERE ".join('AND ', @where_list); + } + } + + # extract update parameter from arg hash + my $update_hash = @{ $arg->{update} }[0]; + my $update_statement = ""; + if( 0 < keys %{ $update_hash } ) { + my @update_list; + while( my ($rest_pram, $rest_val) = each %{ $update_hash } ) { + my $statement = "$rest_pram='@{ $rest_val }[0]'"; + push( @update_list, $statement ); + } + $update_statement .= join(', ', @update_list); + } + + my $sql_statement = "UPDATE $table SET $update_statement $where_statement"; + &create_lock($self,'update_dbentry'); + my $db_answer = $self->{dbh}->do($sql_statement); + &remove_lock($self,'update_dbentry'); + return $db_answer; +} + + +sub del_dbentry { + my $self = shift; + my $arg = shift; + + + # check completeness of function parameter + # extract table statement from arg hash + my $table = $arg->{table}; + if (not defined $table) { + return 1; + } else { + delete $arg->{table}; + } + + # collect select statements + my @del_list; + while (my ($pram, $val) = each %{$arg}) { + if ( $pram eq 'timestamp' ) { + push(@del_list, "$pram < '$val'"); + } else { + push(@del_list, "$pram = '$val'"); + } + } + + my $where_statement; + if( not @del_list ) { + $where_statement = ""; + } else { + $where_statement = "WHERE ".join(' AND ', @del_list); + } + + my $sql_statement = "DELETE FROM $table $where_statement"; + &create_lock($self,'del_dbentry'); + my $db_res = $self->{dbh}->do($sql_statement); + &remove_lock($self,'del_dbentry'); + return $db_res; +} + + +sub get_table_columns { + my $self = shift; + my $table = shift; + my @column_names; + + if(exists $col_names->{$table}) { + @column_names = @{$col_names->{$table}}; + } else { + &create_lock($self,'get_table_columns'); + my @res = @{$self->{dbh}->selectall_arrayref("pragma table_info('$table')")}; + &remove_lock($self,'get_table_columns'); + foreach my $column (@res) { + push(@column_names, @$column[1]); + } + } + return \@column_names; + +} + +sub select_dbentry { + my $self = shift; + my $arg = shift; + + + # check completeness of function parameter + # extract table statement from arg hash + my $table = $arg->{table}; + if (not defined $table) { + return 1; + } else { + delete $arg->{table}; + } + + # collect select statements + my @select_list; + my $sql_statement; + while (my ($pram, $val) = each %{$arg}) { + if ( $pram eq 'timestamp' ) { + push(@select_list, "$pram < '$val'"); + } else { + push(@select_list, "$pram = '$val'"); + } + } + + if (@select_list == 0) { + $sql_statement = "SELECT * FROM '$table'"; + } else { + $sql_statement = "SELECT * FROM '$table' WHERE ".join(' AND ', @select_list); + } + + # query db + &create_lock($self,'select_dbentry'); + my $query_answer = $self->{dbh}->selectall_arrayref($sql_statement); + &remove_lock($self,'select_dbentry'); + + # fetch column list of db and create a hash with column_name->column_value of the select query + my $column_list = &get_table_columns($self, $table); + my $list_len = @{ $column_list } ; + my $answer = {}; + my $hit_counter = 0; + + foreach my $hit ( @{ $query_answer }) { + $hit_counter++; + for ( my $i = 0; $i < $list_len; $i++) { + $answer->{ $hit_counter }->{ @{ $column_list }[$i] } = @{ $hit }[$i]; + } + } + + return $answer; +} + + +sub show_table { + my $self = shift; + my $table_name = shift; + &create_lock($self,'show_table'); + my @res = @{$self->{dbh}->selectall_arrayref( "SELECT * FROM $table_name")}; + &remove_lock($self,'show_table'); + my @answer; + foreach my $hit (@res) { + push(@answer, "hit: ".join(', ', @{$hit})); + } + return join("\n", @answer); +} + + +sub exec_statement { + my $self = shift; + my $sql_statement = shift; + &create_lock($self,'exec_statement'); + my @res = @{$self->{dbh}->selectall_arrayref($sql_statement)}; + &remove_lock($self, 'exec_statement'); + return \@res; +} + +sub get_time { + my ($seconds, $minutes, $hours, $monthday, $month, + $year, $weekday, $yearday, $sommertime) = localtime(time); + $hours = $hours < 10 ? $hours = "0".$hours : $hours; + $minutes = $minutes < 10 ? $minutes = "0".$minutes : $minutes; + $seconds = $seconds < 10 ? $seconds = "0".$seconds : $seconds; + $month+=1; + $month = $month < 10 ? $month = "0".$month : $month; + $monthday = $monthday < 10 ? $monthday = "0".$monthday : $monthday; + $year+=1900; + return "$year$month$monthday$hours$minutes$seconds"; +} + + +1; diff --git a/gosa-si-poe/modules/GosaPackages.pm b/gosa-si-poe/modules/GosaPackages.pm new file mode 100644 index 000000000..86895caeb --- /dev/null +++ b/gosa-si-poe/modules/GosaPackages.pm @@ -0,0 +1,610 @@ +package GosaPackages; + +use Exporter; +@ISA = ("Exporter"); + +use strict; +use warnings; +use GOSA::GosaSupportDaemon; +use IO::Socket::INET; +use XML::Simple; +use File::Spec; +use Data::Dumper; +use GOSA::DBsqlite; +use MIME::Base64; + +BEGIN{} +END{} + +my ($server_activ, $server_ip, $server_mac_address, $server_port, $server_passwd, $max_clients, $server_event_dir); +my ($bus_activ, $bus_passwd, $bus_ip, $bus_port); +my ($gosa_activ, $gosa_ip, $gosa_mac_address, $gosa_port, $gosa_passwd, $network_interface); +my ($job_queue_timeout, $job_queue_file_name); + +my $gosa_server; + +my %cfg_defaults = +("general" => + {"job_queue_file_name" => [\$job_queue_file_name, '/var/lib/gosa-si/jobs.db'], + }, +"server" => + {"server_activ" => [\$server_activ, "on"], + "server_ip" => [\$server_ip, "0.0.0.0"], + "server_port" => [\$server_port, "20081"], + "server_passwd" => [\$server_passwd, ""], + "max_clients" => [\$max_clients, 100], + "server_event_dir" => [\$server_event_dir, '/usr/lib/gosa-si/server/events'], + }, +"bus" => + {"bus_activ" => [\$bus_activ, "on"], + "bus_passwd" => [\$bus_passwd, ""], + "bus_ip" => [\$bus_ip, "0.0.0.0"], + "bus_port" => [\$bus_port, "20080"], + }, +"gosa" => + {"gosa_activ" => [\$gosa_activ, "on"], + "gosa_ip" => [\$gosa_ip, "0.0.0.0"], + "gosa_port" => [\$gosa_port, "20082"], + "gosa_passwd" => [\$gosa_passwd, "none"], + }, +); + + +## START ########################## + +# read configfile and import variables +&read_configfile(); +$network_interface= &get_interface_for_ip($server_ip); +$gosa_mac_address= &get_mac($network_interface); + +# complete addresses +my $server_address = "$server_ip:$server_port"; +my $bus_address = "$bus_ip:$bus_port"; +my $gosa_address = "$gosa_ip:$gosa_port"; + +# create general settings for this module +my $gosa_cipher = &create_ciphering($gosa_passwd); +my $xml = new XML::Simple(); + +# open gosa socket +if ($gosa_activ eq "on") { + &main::daemon_log(" ",1); + $gosa_server = IO::Socket::INET->new(LocalPort => $gosa_port, + Type => SOCK_STREAM, + Reuse => 1, + Listen => 1, + ); + if (not defined $gosa_server) { + &main::daemon_log("cannot start tcp server at $gosa_port for communication to gosa: $@", 1); + die; + } else { + &main::daemon_log("start server for communication to gosa: $gosa_address", 1); + + } +} + +# create gosa job queue as a SQLite DB +my $table_name = "jobs"; +my $sqlite = GOSA::DBsqlite->new($job_queue_file_name); + + + + +## FUNCTIONS ################################################################# + +sub get_module_info { + my @info = ($gosa_address, + $gosa_passwd, + $gosa_server, + $gosa_activ, + "socket", + ); + return \@info; +} + + +#=== FUNCTION ================================================================ +# NAME: read_configfile +# PARAMETERS: cfg_file - string - +# RETURNS: nothing +# DESCRIPTION: read cfg_file and set variables +#=============================================================================== +sub read_configfile { + my $cfg; + if( defined( $main::cfg_file) && ( length($main::cfg_file) > 0 )) { + if( -r $main::cfg_file ) { + $cfg = Config::IniFiles->new( -file => $main::cfg_file ); + } else { + print STDERR "Couldn't read config file!"; + } + } else { + $cfg = Config::IniFiles->new() ; + } + foreach my $section (keys %cfg_defaults) { + foreach my $param (keys %{$cfg_defaults{ $section }}) { + my $pinfo = $cfg_defaults{ $section }{ $param }; + ${@$pinfo[0]} = $cfg->val( $section, $param, @$pinfo[1] ); + } + } +} + +#=== FUNCTION ================================================================ +# NAME: get_interface_for_ip +# PARAMETERS: ip address (i.e. 192.168.0.1) +# RETURNS: array: list of interfaces if ip=0.0.0.0, matching interface if found, undef else +# DESCRIPTION: Uses proc fs (/proc/net/dev) to get list of interfaces. +#=============================================================================== +sub get_interface_for_ip { + my $result; + my $ip= shift; + if ($ip && length($ip) > 0) { + my @ifs= &get_interfaces(); + if($ip eq "0.0.0.0") { + $result = "all"; + } else { + foreach (@ifs) { + my $if=$_; + if(get_ip($if) eq $ip) { + $result = $if; + } + } + } + } + return $result; +} + +#=== FUNCTION ================================================================ +# NAME: get_interfaces +# PARAMETERS: none +# RETURNS: (list of interfaces) +# DESCRIPTION: Uses proc fs (/proc/net/dev) to get list of interfaces. +#=============================================================================== +sub get_interfaces { + my @result; + my $PROC_NET_DEV= ('/proc/net/dev'); + + open(PROC_NET_DEV, "<$PROC_NET_DEV") + or die "Could not open $PROC_NET_DEV"; + + my @ifs = ; + + close(PROC_NET_DEV); + + # Eat first two line + shift @ifs; + shift @ifs; + + chomp @ifs; + foreach my $line(@ifs) { + my $if= (split /:/, $line)[0]; + $if =~ s/^\s+//; + push @result, $if; + } + + return @result; +} + +#=== FUNCTION ================================================================ +# NAME: get_mac +# PARAMETERS: interface name (i.e. eth0) +# RETURNS: (mac address) +# DESCRIPTION: Uses ioctl to get mac address directly from system. +#=============================================================================== +sub get_mac { + my $ifreq= shift; + my $result; + if ($ifreq && length($ifreq) > 0) { + if($ifreq eq "all") { + $result = "00:00:00:00:00:00"; + } else { + my $SIOCGIFHWADDR= 0x8927; # man 2 ioctl_list + + # A configured MAC Address should always override a guessed value + if ($gosa_mac_address and length($gosa_mac_address) > 0) { + $result= $gosa_mac_address; + } + + socket SOCKET, PF_INET, SOCK_DGRAM, getprotobyname('ip') + or die "socket: $!"; + + if(ioctl SOCKET, $SIOCGIFHWADDR, $ifreq) { + my ($if, $mac)= unpack 'h36 H12', $ifreq; + + if (length($mac) > 0) { + $mac=~ m/^([0-9a-f][0-9a-f])([0-9a-f][0-9a-f])([0-9a-f][0-9a-f])([0-9a-f][0-9a-f])([0-9a-f][0-9a-f])([0-9a-f][0-9a-f])$/; + $mac= sprintf("%s:%s:%s:%s:%s:%s", $1, $2, $3, $4, $5, $6); + $result = $mac; + } + } + } + } + return $result; +} + +#=== FUNCTION ================================================================ +# NAME: get_ip +# PARAMETERS: interface name (i.e. eth0) +# RETURNS: (ip address) +# DESCRIPTION: Uses ioctl to get ip address directly from system. +#=============================================================================== +sub get_ip { + my $ifreq= shift; + my $result= ""; + my $SIOCGIFADDR= 0x8915; # man 2 ioctl_list + my $proto= getprotobyname('ip'); + + socket SOCKET, PF_INET, SOCK_DGRAM, $proto + or die "socket: $!"; + + if(ioctl SOCKET, $SIOCGIFADDR, $ifreq) { + my ($if, $sin) = unpack 'a16 a16', $ifreq; + my ($port, $addr) = sockaddr_in $sin; + my $ip = inet_ntoa $addr; + + if ($ip && length($ip) > 0) { + $result = $ip; + } + } + + return $result; +} + +#=== FUNCTION ================================================================ +# NAME: open_socket +# PARAMETERS: PeerAddr string something like 192.168.1.1 or 192.168.1.1:10000 +# [PeerPort] string necessary if port not appended by PeerAddr +# RETURNS: socket IO::Socket::INET +# DESCRIPTION: open a socket to PeerAddr +#=============================================================================== +sub open_socket { + my ($PeerAddr, $PeerPort) = @_ ; + if(defined($PeerPort)){ + $PeerAddr = $PeerAddr.":".$PeerPort; + } + my $socket; + $socket = new IO::Socket::INET(PeerAddr => $PeerAddr , + Porto => "tcp" , + Type => SOCK_STREAM, + Timeout => 5, + ); + if(not defined $socket) { + return; + } + &main::daemon_log("open_socket to: $PeerAddr", 7); + return $socket; +} + + +#=== FUNCTION ================================================================ +# NAME: process_incoming_msg +# PARAMETERS: crypted_msg - string - incoming crypted message +# RETURNS: nothing +# DESCRIPTION: handels the proceeded distribution to the appropriated functions +#=============================================================================== +sub process_incoming_msg { + my ($crypted_msg) = @_ ; + &main::daemon_log("Got message $crypted_msg", 5); + if( (not(defined($crypted_msg))) || (length($crypted_msg) <= 0)) { + &main::daemon_log("function 'process_incoming_msg': got no msg", 7); + return; + } + + $crypted_msg =~ /^([\s\S]*?)\.(\d{1,3}?)\.(\d{1,3}?)\.(\d{1,3}?)\.(\d{1,3}?)$/; + $crypted_msg = $1; + my $host = sprintf("%s.%s.%s.%s", $2, $3, $4, $5); + + # collect addresses from possible incoming clients + # only gosa is allowd as incoming client + &main::daemon_log("GosaPackages: host_key: $host", 7); + &main::daemon_log("GosaPackages: key_passwd: $gosa_passwd", 7); + + $gosa_cipher = &create_ciphering($gosa_passwd); + + # determine the correct passwd for deciphering of the incoming msgs + my $msg = ""; + my $msg_hash; + eval{ + $msg = &decrypt_msg($crypted_msg, $gosa_cipher); + &main::daemon_log("GosaPackages: decrypted_msg: \n$msg", 7); + + $msg_hash = $xml->XMLin($msg, ForceArray=>1); + }; + if($@) { + &main::daemon_log("WARNING: GosaPackages do not understand the message:", 5); + &main::daemon_log("$@", 7); + return; + } + + my $header = @{$msg_hash->{header}}[0]; + + &main::daemon_log("GosaPackages: receive '$header' from $host", 1); + + my $out_msg; + if ($header =~ /^job_/) { + $out_msg = &process_job_msg($msg, $msg_hash); + } elsif ($header =~ /^gosa_/) { + $out_msg = &process_gosa_msg($msg, $header); + } else { + &main::daemon_log("ERROR: $header is not a valid GosaPackage-header, need a 'job_' or a 'gosa_' prefix"); + } + + if (not defined $out_msg) { + return; + } + + if ($out_msg =~ /(\d*?)<\/jobdb_id>/) { + my $job_id = $1; + my $sql = "BEGIN TRANSATION; UPDATE '".$main::job_queue_table_name. + "' SET status='done', result='".$out_msg. + "' WHERE id='$job_id'; COMMIT;"; + my $res = $main::job_db->exec_statement($sql); + return; + + } else { + + my $out_cipher = &create_ciphering($gosa_passwd); + $out_msg = &encrypt_msg($out_msg, $out_cipher); + return $out_msg; + } + +} + +sub process_gosa_msg { + my ($msg, $header) = @_ ; + my $out_msg; + $header =~ s/gosa_//; + + # decide wether msg is a core function or a event handler + if ( $header eq 'query_jobdb') { $out_msg = &query_jobdb } + elsif ($header eq 'delete_jobdb_entry') { $out_msg = &delete_jobdb_entry } + elsif ($header eq 'clear_jobdb') { $out_msg = &clear_jobdb } + elsif ($header eq 'update_status_jobdb_entry' ) { $out_msg = &update_status_jobdb_entry } + elsif ($header eq 'update_timestamp_jobdb_entry' ) { $out_msg = &update_timestamp_jobdb_entry } + else { + # msg could not be assigned to core function + # fetch all available eventhandler under $server_event_dir + opendir (DIR, $server_event_dir) or &main::daemon_log("ERROR cannot open $server_event_dir: $!\n", 1) and return; + while (defined (my $file = readdir (DIR))) { + if (not $file eq $header) { + next; + } + # try to deliver incoming msg to eventhandler + my $cmd = File::Spec->join($server_event_dir, $header)." '$msg'"; + &main::daemon_log("GosaPackages: execute event_handler $header", 3); + &main::daemon_log("GosaPackages: cmd: $cmd", 7); + + $out_msg = ""; + open(PIPE, "$cmd 2>&1 |"); + while() { + $out_msg.=$_; + } + close(PIPE); + &main::daemon_log("GosaPackages: answer of cmd: $out_msg", 5); + last; + } + } + + # if delivery not possible raise error and return + if (not defined $out_msg) { + &main::daemon_log("ERROR: GosaPackages: no event handler or core function defined for $header", 1); + } elsif ($out_msg eq "") { + &main::daemon_log("ERROR: GosaPackages got not answer from event_handler $header", 1); + } + return $out_msg; + +} + + +sub process_job_msg { + my ($msg, $msg_hash)= @_ ; + + my $header = @{$msg_hash->{header}}[0]; + $header =~ s/job_//; + + # check wether mac address is already known in known_daemons or known_clients + my $target = 'none'; + + # add job to job queue + my $func_dic = {table=>$main::job_queue_table_name, + primkey=>'id', + timestamp=>@{$msg_hash->{timestamp}}[0], + status=>'waiting', + result=>'none', + headertag=>$header, + targettag=>$target, + xmlmessage=>$msg, + macaddress=>@{$msg_hash->{mac}}[0], + }; + my $res = $main::job_db->add_dbentry($func_dic); + if (not $res == 0) { + &main::daemon_log("ERROR: GosaPackages: process_job_msg: $res", 1); + } + + &main::daemon_log("GosaPackages: $header job successfully added to job queue", 3); + return "<1>$res"; + +} + + +sub db_res_2_xml { + my ($db_res) = @_ ; + + my $xml = ""; + + while ( my ($hit, $hash) = each %{ $db_res } ) { + $xml .= "\n"; + + while ( my ($column_name, $column_value) = each %{$hash} ) { + $xml .= "<$column_name>"; + my $xml_content; + if( $column_name eq "xmlmessage" ) { + $xml_content = &encode_base64($column_value); + } else { + $xml_content = $column_value; + } + $xml .= $xml_content; + $xml .= ""; + } + + $xml .= ""; + } + + $xml .= ""; + return $xml; +} + + +## CORE FUNCTIONS ############################################################ + +sub query_jobdb { + my ($msg) = @_; + my $msg_hash = &transform_msg2hash($msg); + + # prepare query sql statement + my @where = @{$msg_hash->{where}}; + my $where_hash = {table=>$main::job_queue_table_name }; + foreach my $where_pram (@where) { + my $where_val = @{$msg_hash->{$where_pram}}[0]; + if (defined $where_val) { + $where_hash->{$where_pram} = $where_val; + } + } + + # execute db query + my $res_hash = $main::job_db->select_dbentry($where_hash); + + my $out_xml = &db_res_2_xml($res_hash); + return $out_xml; +} + +sub delete_jobdb_entry { + my ($msg) = @_ ; + my $msg_hash = &transform_msg2hash($msg); + + # prepare query sql statement + my @where = @{$msg_hash->{where}}; + my $where_hash = {table=>$main::job_queue_table_name }; + foreach my $where_pram (@where) { + my $where_val = @{$msg_hash->{$where_pram}}[0]; + if (defined $where_val) { + $where_hash->{$where_pram} = $where_val; + } + } + + # execute db query + my $db_res = $main::job_db->del_dbentry($where_hash); + + my $res; + if( $db_res > 0 ) { + $res = 0 ; + } else { + $res = 1; + } + + # prepare xml answer + my $out_xml = "$res"; + return $out_xml; + +} + +sub clear_jobdb { + my ($msg) = @_ ; + my $msg_hash = &transform_msg2hash($msg); + + my $where_hash = {table=>$main::job_queue_table_name }; + + # execute db query + my $db_res = $main::job_db->del_dbentry($where_hash); + print STDERR "db_res=$db_res\n"; + my $res; + if( $db_res eq '0E0' ) { + $res = 0 ; + } else { + $res = 1; + } + + # prepare xml answer + my $out_xml = "$res"; + return $out_xml; +} + +sub update_status_jobdb_entry { + my ($msg) = @_ ; + my $msg_hash = &transform_msg2hash($msg); + + # prepare query sql statement + my $update_hash = {table=>$main::job_queue_table_name }; + if( exists $msg_hash->{where} ) { + $update_hash->{where} = $msg_hash->{where}; + } else { + $update_hash->{where} = []; + } + + if( not exists $msg_hash->{update}[0]->{status} ) { + return "1"; + } + $update_hash->{update} = [ { status=>$msg_hash->{update}[0]->{status} } ]; + + # execute db query + my $db_res = $main::job_db->update_dbentry($update_hash); + + # transform db answer to error returnment + my $res; + if( $db_res > 0 ) { + $res = 0 ; + } else { + $res = 1; + } + + # prepare xml answer + my $out_xml = "$res"; + return $out_xml; +} + +sub update_timestamp_jobdb_entry { + my ($msg) = @_ ; + my $msg_hash = &transform_msg2hash($msg); + + # prepare query sql statement + my $update_hash = {table=>$main::job_queue_table_name }; + if( exists $msg_hash->{where} ) { + $update_hash->{where} = $msg_hash->{where}; + } else { + $update_hash->{where} = []; + } + + if( not exists $msg_hash->{update}[0]->{timestamp} ) { + return "1"; + } + + $update_hash->{update} = [ { timestamp=>$msg_hash->{update}[0]->{timestamp} } ]; + + # execute db query + my $db_res = $main::job_db->update_dbentry($update_hash); + + # transform db answer to error returnment + my $res; + if( $db_res > 0 ) { + $res = 0 ; + } else { + $res = 1; + } + + # prepare xml answer + my $out_xml = "$res"; + return $out_xml; + +} + + +1; + + + + + + + + + + diff --git a/gosa-si-poe/modules/GosaSupportDaemon.pm b/gosa-si-poe/modules/GosaSupportDaemon.pm new file mode 100644 index 000000000..cf26ca49f --- /dev/null +++ b/gosa-si-poe/modules/GosaSupportDaemon.pm @@ -0,0 +1,300 @@ +package GOSA::GosaSupportDaemon; + +use Exporter; +@ISA = qw(Exporter); +@EXPORT = qw(create_xml_hash send_msg_hash2address get_content_from_xml_hash add_content2xml_hash create_xml_string encrypt_msg decrypt_msg create_ciphering transform_msg2hash get_time send_msg); + +use strict; +use warnings; +use IO::Socket::INET; +use Crypt::Rijndael; +use Digest::MD5 qw(md5 md5_hex md5_base64); +use MIME::Base64; +use XML::Simple; + + + +BEGIN {} + +END {} + +### Start ###################################################################### + +my $xml = new XML::Simple(); + +sub process_incoming_msg { + return; +} + +sub daemon_log { + my ($msg, $level) = @_ ; + &main::daemon_log($msg, $level); + return; +} + + +#=== FUNCTION ================================================================ +# NAME: create_xml_hash +# PARAMETERS: header - string - message header (required) +# source - string - where the message come from (required) +# target - string - where the message should go to (required) +# [header_value] - string - something usefull (optional) +# RETURNS: hash - hash - nomen est omen +# DESCRIPTION: creates a key-value hash, all values are stored in a array +#=============================================================================== +sub create_xml_hash { + my ($header, $source, $target, $header_value) = @_; + my $hash = { + header => [$header], + source => [$source], + target => [$target], + $header => [$header_value], + }; + return $hash +} + + +sub transform_msg2hash { + my ($msg) = @_ ; + my $hash = $xml->XMLin($msg, ForceArray=>1); + + # xml tags without a content are created as an empty hash + # substitute it with an empty list + while( my ($xml_tag, $xml_content) = each %{ $hash } ) { + if( 1 == @{ $xml_content } ) { + # there is only one element in xml_content list ... + my $element = @{ $xml_content }[0]; + if( ref($element) eq "HASH" ) { + # and this element is an hash ... + my $len_element = keys %{ $element }; + if( $len_element == 0 ) { + # and this hash is empty, then substitute the xml_content + # with an empty string in list + $hash->{$xml_tag} = [ "none" ]; + } + } + } + } + + return $hash; +} + + +#=== FUNCTION ================================================================ +# NAME: send_msg_hash2address +# PARAMETERS: msg_hash - hash - xml_hash created with function create_xml_hash +# PeerAddr string - socket address to send msg +# PeerPort string - socket port, if not included in socket address +# RETURNS: nothing +# DESCRIPTION: ???? +#=============================================================================== +sub send_msg_hash2address ($$$){ + my ($msg_hash, $address, $passwd) = @_ ; + + # fetch header for logging + my $header = @{$msg_hash->{header}}[0]; + + # generate xml string + my $msg_xml = &create_xml_string($msg_hash); + + # create ciphering object + my $act_cipher = &create_ciphering($passwd); + + # encrypt xml msg + my $crypted_msg = &encrypt_msg($msg_xml, $act_cipher); + + # opensocket + my $socket = &open_socket($address); + if(not defined $socket){ + daemon_log("cannot send '$header'-msg to $address , server not reachable", 5); + return 1; + } + + # send xml msg + print $socket $crypted_msg."\n"; + + close $socket; + + daemon_log("send '$header'-msg to $address", 1); + daemon_log("message:\n$msg_xml", 8); + return 0; +} + + +#=== FUNCTION ================================================================ +# NAME: get_content_from_xml_hash +# PARAMETERS: xml_ref - ref - reference of the xml hash +# element - string - key of the value you want +# RETURNS: value - string - if key is either header, target or source +# value - list - for all other keys in xml hash +# DESCRIPTION: +#=============================================================================== +sub get_content_from_xml_hash { + my ($xml_ref, $element) = @_ ; + #my $result = $main::xml_ref->{$element}; + #if( $element eq "header" || $element eq "target" || $element eq "source") { + # return @$result[0]; + #} + my @result = $xml_ref->{$element}; + return \@result; +} + + +#=== FUNCTION ================================================================ +# NAME: add_content2xml_hash +# PARAMETERS: xml_ref - ref - reference to a hash from function create_xml_hash +# element - string - key for the hash +# content - string - value for the hash +# RETURNS: nothing +# DESCRIPTION: add key-value pair to xml_ref, if key alread exists, +# then append value to list +#=============================================================================== +sub add_content2xml_hash { + my ($xml_ref, $element, $content) = @_; + if(not exists $$xml_ref{$element} ) { + $$xml_ref{$element} = []; + } + my $tmp = $$xml_ref{$element}; + push(@$tmp, $content); + return; +} + + +#=== FUNCTION ================================================================ +# NAME: create_xml_string +# PARAMETERS: xml_hash - hash - hash from function create_xml_hash +# RETURNS: xml_string - string - xml string representation of the hash +# DESCRIPTION: transform the hash to a string using XML::Simple module +#=============================================================================== +sub create_xml_string { + my ($xml_hash) = @_ ; + my $xml_string = $xml->XMLout($xml_hash, RootName => 'xml'); + #$xml_string =~ s/[\n]+//g; + #daemon_log("create_xml_string:",7); + #daemon_log("$xml_string\n", 7); + return $xml_string; +} + + +#=== FUNCTION ================================================================ +# NAME: encrypt_msg +# PARAMETERS: msg - string - message to encrypt +# my_cipher - ref - reference to a Crypt::Rijndael object +# RETURNS: crypted_msg - string - crypted message +# DESCRIPTION: crypts the incoming message with the Crypt::Rijndael module +#=============================================================================== +sub encrypt_msg { + my ($msg, $my_cipher) = @_; + if(not defined $my_cipher) { print "no cipher object\n"; } + $msg = "\0"x(16-length($msg)%16).$msg; + $msg = $my_cipher->encrypt($msg); + chomp($msg = &encode_base64($msg)); + return $msg; +} + + +#=== FUNCTION ================================================================ +# NAME: decrypt_msg +# PARAMETERS: crypted_msg - string - message to decrypt +# my_cipher - ref - reference to a Crypt::Rijndael object +# RETURNS: msg - string - decrypted message +# DESCRIPTION: decrypts the incoming message with the Crypt::Rijndael module +#=============================================================================== +sub decrypt_msg { + my ($msg, $my_cipher) = @_ ; + if(defined $msg && defined $my_cipher) { + $msg = &decode_base64($msg); + } + $msg = $my_cipher->decrypt($msg); + $msg =~ s/\0*//g; + return $msg; +} + + +#=== FUNCTION ================================================================ +# NAME: create_ciphering +# PARAMETERS: passwd - string - used to create ciphering +# RETURNS: cipher - object +# DESCRIPTION: creates a Crypt::Rijndael::MODE_CBC object with passwd as key +#=============================================================================== +sub create_ciphering { + my ($passwd) = @_; + $passwd = substr(md5_hex("$passwd") x 32, 0, 32); + my $iv = substr(md5_hex('GONICUS GmbH'),0, 16); + + #daemon_log("iv: $iv", 7); + #daemon_log("key: $passwd", 7); + my $my_cipher = Crypt::Rijndael->new($passwd , Crypt::Rijndael::MODE_CBC()); + $my_cipher->set_iv($iv); + return $my_cipher; +} + + +#=== FUNCTION ================================================================ +# NAME: open_socket +# PARAMETERS: PeerAddr string something like 192.168.1.1 or 192.168.1.1:10000 +# [PeerPort] string necessary if port not appended by PeerAddr +# RETURNS: socket IO::Socket::INET +# DESCRIPTION: open a socket to PeerAddr +#=============================================================================== +sub open_socket { + my ($PeerAddr, $PeerPort) = @_ ; + if(defined($PeerPort)){ + $PeerAddr = $PeerAddr.":".$PeerPort; + } + my $socket; + $socket = new IO::Socket::INET(PeerAddr => $PeerAddr, + Porto => "tcp", + Type => SOCK_STREAM, + Timeout => 5, + ); + if(not defined $socket) { + return; + } + &daemon_log("open_socket: $PeerAddr", 7); + return $socket; +} + + +sub get_time { + my ($seconds, $minutes, $hours, $monthday, $month, + $year, $weekday, $yearday, $sommertime) = localtime(time); + $hours = $hours < 10 ? $hours = "0".$hours : $hours; + $minutes = $minutes < 10 ? $minutes = "0".$minutes : $minutes; + $seconds = $seconds < 10 ? $seconds = "0".$seconds : $seconds; + $month+=1; + $month = $month < 10 ? $month = "0".$month : $month; + $monthday = $monthday < 10 ? $monthday = "0".$monthday : $monthday; + $year+=1900; + return "$year$month$monthday$hours$minutes$seconds"; + +} + + +#=== FUNCTION ================================================================ +# NAME: send_msg +# DESCRIPTION: Send a message to a destination +# PARAMETERS: [header] Name of the header +# [from] sender ip +# [to] recipient ip +# [data] Hash containing additional attributes for the xml +# package +# RETURNS: nothing +#=============================================================================== +sub send_msg ($$$$$) { + my ($header, $from, $to, $data, $hostkey) = @_; + + my $out_hash = &create_xml_hash($header, $from, $to); + + while ( my ($key, $value) = each(%$data) ) { + if(ref($value) eq 'ARRAY'){ + map(&add_content2xml_hash($out_hash, $key, $_), @$value); + } else { + &add_content2xml_hash($out_hash, $key, $value); + } + } + + &send_msg_hash2address($out_hash, $to, $hostkey); +} + +1; diff --git a/gosa-si-poe/modules/ServerPackages.pm b/gosa-si-poe/modules/ServerPackages.pm new file mode 100644 index 000000000..d20b66489 --- /dev/null +++ b/gosa-si-poe/modules/ServerPackages.pm @@ -0,0 +1,890 @@ +package ServerPackages; + +use Exporter; +@ISA = ("Exporter"); + +# Each module has to have a function 'process_incoming_msg'. This function works as a interface to gosa-sd and receives the msg hash from gosa-sd. 'process_incoming_function checks, wether it has a function to process the incoming msg and forward the msg to it. + + +use strict; +use warnings; +use GOSA::GosaSupportDaemon; +use IO::Socket::INET; +use XML::Simple; +use Data::Dumper; +use Net::LDAP; +use Socket qw/PF_INET SOCK_DGRAM inet_ntoa sockaddr_in/; + +BEGIN{} +END {} + +my ($known_clients_file_name); +my ($server_activ, $server_ip, $server_mac_address, $server_port, $server_passwd, $max_clients, $ldap_uri, $ldap_base, $ldap_admin_dn, $ldap_admin_password); +my ($bus_activ, $bus_passwd, $bus_ip, $bus_port); +my $server; +my $network_interface; +my $no_bus; +my (@ldap_cfg, @pam_cfg, @nss_cfg, $goto_admin, $goto_secret); + + +my %cfg_defaults = +( +"server" => + {"server_activ" => [\$server_activ, "on"], + "server_ip" => [\$server_ip, "0.0.0.0"], + "server_mac_address" => [\$server_mac_address, ""], + "server_port" => [\$server_port, "20081"], + "server_passwd" => [\$server_passwd, ""], + "max_clients" => [\$max_clients, 100], + "ldap_uri" => [\$ldap_uri, ""], + "ldap_base" => [\$ldap_base, ""], + "ldap_admin_dn" => [\$ldap_admin_dn, ""], + "ldap_admin_password" => [\$ldap_admin_password, ""], + }, +"bus" => + {"bus_activ" => [\$bus_activ, "on"], + "bus_passwd" => [\$bus_passwd, ""], + "bus_ip" => [\$bus_ip, ""], + "bus_port" => [\$bus_port, "20080"], + }, +); + +### START ##################################################################### + +# read configfile and import variables +&read_configfile(); + +# detect interfaces and mac address +$network_interface= &get_interface_for_ip($server_ip); +$server_mac_address= &get_mac($network_interface); + +&main::daemon_log("server ip address detected: $server_ip", 1); +&main::daemon_log("server mac address detected: $server_mac_address", 1); + +# complete addresses +my $server_address = "$server_ip:$server_port"; +my $bus_address = "$bus_ip:$bus_port"; + +# create general settings for this module +my $xml = new XML::Simple(); + +## open server socket +#if($server_activ eq "on"){ +# &main::daemon_log(" ", 1); +# $server = IO::Socket::INET->new(LocalPort => $server_port, +# Type => SOCK_STREAM, +# Reuse => 1, +# Listen => 20, +# ); +# if(not defined $server){ +# &main::daemon_log("cannot be a tcp server at $server_port : $@"); +# die; +# } else { +# &main::daemon_log("start server: $server_address", 1); +# } +#} +# +# +## register at bus +#if ($main::no_bus > 0) { +# $bus_activ = "off" +#} +#if($bus_activ eq "on") { +# &main::daemon_log(" ", 1); +# ®ister_at_bus(); +#} + +### functions ################################################################# + + +sub get_module_info { + my @info = ($server_address, + $server_passwd, + $server, + $server_activ, + "socket", + ); + return \@info; +} + + +#=== FUNCTION ================================================================ +# NAME: read_configfile +# PARAMETERS: cfg_file - string - +# RETURNS: nothing +# DESCRIPTION: read cfg_file and set variables +#=============================================================================== +sub read_configfile { + my $cfg; + if( defined( $main::cfg_file) && ( length($main::cfg_file) > 0 )) { + if( -r $main::cfg_file ) { + $cfg = Config::IniFiles->new( -file => $main::cfg_file ); + } else { + print STDERR "Couldn't read config file!"; + } + } else { + $cfg = Config::IniFiles->new() ; + } + foreach my $section (keys %cfg_defaults) { + foreach my $param (keys %{$cfg_defaults{ $section }}) { + my $pinfo = $cfg_defaults{ $section }{ $param }; + ${@$pinfo[0]} = $cfg->val( $section, $param, @$pinfo[1] ); + } + } + + # Read non predefined sections + my $param; + if ($cfg->SectionExists('ldap')){ + foreach $param ($cfg->Parameters('ldap')){ + push (@ldap_cfg, "$param ".$cfg->val('ldap', $param)); + } + } + if ($cfg->SectionExists('pam_ldap')){ + foreach $param ($cfg->Parameters('pam_ldap')){ + push (@pam_cfg, "$param ".$cfg->val('pam_ldap', $param)); + } + } + if ($cfg->SectionExists('nss_ldap')){ + foreach $param ($cfg->Parameters('nss_ldap')){ + push (@nss_cfg, "$param ".$cfg->val('nss_ldap', $param)); + } + } + if ($cfg->SectionExists('goto')){ + $goto_admin= $cfg->val('goto', 'terminal_admin'); + $goto_secret= $cfg->val('goto', 'terminal_secret'); + } else { + $goto_admin= undef; + $goto_secret= undef; + } + +} + +#=== FUNCTION ================================================================ +# NAME: get_interface_for_ip +# PARAMETERS: ip address (i.e. 192.168.0.1) +# RETURNS: array: list of interfaces if ip=0.0.0.0, matching interface if found, undef else +# DESCRIPTION: Uses proc fs (/proc/net/dev) to get list of interfaces. +#=============================================================================== +sub get_interface_for_ip { + my $result; + my $ip= shift; + if ($ip && length($ip) > 0) { + my @ifs= &get_interfaces(); + if($ip eq "0.0.0.0") { + $result = "all"; + } else { + foreach (@ifs) { + my $if=$_; + if(get_ip($if) eq $ip) { + $result = $if; + } + } + } + } + return $result; +} + +#=== FUNCTION ================================================================ +# NAME: get_interfaces +# PARAMETERS: none +# RETURNS: (list of interfaces) +# DESCRIPTION: Uses proc fs (/proc/net/dev) to get list of interfaces. +#=============================================================================== +sub get_interfaces { + my @result; + my $PROC_NET_DEV= ('/proc/net/dev'); + + open(PROC_NET_DEV, "<$PROC_NET_DEV") + or die "Could not open $PROC_NET_DEV"; + + my @ifs = ; + + close(PROC_NET_DEV); + + # Eat first two line + shift @ifs; + shift @ifs; + + chomp @ifs; + foreach my $line(@ifs) { + my $if= (split /:/, $line)[0]; + $if =~ s/^\s+//; + push @result, $if; + } + + return @result; +} + +#=== FUNCTION ================================================================ +# NAME: get_mac +# PARAMETERS: interface name (i.e. eth0) +# RETURNS: (mac address) +# DESCRIPTION: Uses ioctl to get mac address directly from system. +#=============================================================================== +sub get_mac { + my $ifreq= shift; + my $result; + if ($ifreq && length($ifreq) > 0) { + if($ifreq eq "all") { + $result = "00:00:00:00:00:00"; + } else { + my $SIOCGIFHWADDR= 0x8927; # man 2 ioctl_list + + # A configured MAC Address should always override a guessed value + if ($server_mac_address and length($server_mac_address) > 0) { + $result= $server_mac_address; + } + + socket SOCKET, PF_INET, SOCK_DGRAM, getprotobyname('ip') + or die "socket: $!"; + + if(ioctl SOCKET, $SIOCGIFHWADDR, $ifreq) { + my ($if, $mac)= unpack 'h36 H12', $ifreq; + + if (length($mac) > 0) { + $mac=~ m/^([0-9a-f][0-9a-f])([0-9a-f][0-9a-f])([0-9a-f][0-9a-f])([0-9a-f][0-9a-f])([0-9a-f][0-9a-f])([0-9a-f][0-9a-f])$/; + $mac= sprintf("%s:%s:%s:%s:%s:%s", $1, $2, $3, $4, $5, $6); + $result = $mac; + } + } + } + } + return $result; +} + +#=== FUNCTION ================================================================ +# NAME: get_ip +# PARAMETERS: interface name (i.e. eth0) +# RETURNS: (ip address) +# DESCRIPTION: Uses ioctl to get ip address directly from system. +#=============================================================================== +sub get_ip { + my $ifreq= shift; + my $result= ""; + my $SIOCGIFADDR= 0x8915; # man 2 ioctl_list + my $proto= getprotobyname('ip'); + + socket SOCKET, PF_INET, SOCK_DGRAM, $proto + or die "socket: $!"; + + if(ioctl SOCKET, $SIOCGIFADDR, $ifreq) { + my ($if, $sin) = unpack 'a16 a16', $ifreq; + my ($port, $addr) = sockaddr_in $sin; + my $ip = inet_ntoa $addr; + + if ($ip && length($ip) > 0) { + $result = $ip; + } + } + + return $result; +} + +#=== FUNCTION ================================================================ +# NAME: open_socket +# PARAMETERS: PeerAddr string something like 192.168.1.1 or 192.168.1.1:10000 +# [PeerPort] string necessary if port not appended by PeerAddr +# RETURNS: socket IO::Socket::INET +# DESCRIPTION: open a socket to PeerAddr +#=============================================================================== +#sub open_socket { +# my ($PeerAddr, $PeerPort) = @_ ; +# if(defined($PeerPort)){ +# $PeerAddr = $PeerAddr.":".$PeerPort; +# } +# my $socket; +# $socket = new IO::Socket::INET(PeerAddr => $PeerAddr , +# Porto => "tcp" , +# Type => SOCK_STREAM, +# Timeout => 5, +# ); +# if(not defined $socket) { +# return; +# } +# &main::daemon_log("open_socket to: $PeerAddr", 7); +# return $socket; +#} + +#=== FUNCTION ================================================================ +# NAME: register_at_bus +# PARAMETERS: nothing +# RETURNS: nothing +# DESCRIPTION: creates an entry in known_daemons and send a 'here_i_am' msg to bus +#=============================================================================== +sub register_at_bus { + + # add bus to known_server_db + my $res = $main::known_server_db->add_dbentry( {table=>'known_server', + primkey=>'hostname', + hostname=>$bus_address, + status=>'bus', + hostkey=>$bus_passwd, + timestamp=>&get_time, + } ); + my $msg_hash = &create_xml_hash("here_i_am", $server_address, $bus_address); + my $answer = ""; + $answer = &send_msg_hash2address($msg_hash, $bus_address, $bus_passwd); + if ($answer == 0) { + &main::daemon_log("register at bus: $bus_address", 1); + } else { + &main::daemon_log("unable to send 'register'-msg to bus '$bus_address': $answer", 1); + } + return; +} + +#=== FUNCTION ================================================================ +# NAME: process_incoming_msg +# PARAMETERS: crypted_msg - string - incoming crypted message +# RETURNS: nothing +# DESCRIPTION: handels the proceeded distribution to the appropriated functions +#=============================================================================== +sub process_incoming_msg { + my ($crypted_msg) = @_ ; + if(not defined $crypted_msg) { + &main::daemon_log("function 'process_incoming_msg': got no msg", 7); + } + + &main::daemon_log("ServerPackages: incoming msg: \n$crypted_msg", 8); + + $crypted_msg =~ /^([\s\S]*?)\.(\d{1,3}?)\.(\d{1,3}?)\.(\d{1,3}?)\.(\d{1,3}?)$/; + $crypted_msg = $1; + my $host="0.0.0.0"; + if($1 && $2 && $3 && $4) { + $host = sprintf("%s.%s.%s.%s", $2, $3, $4, $5); + } + + my $msg; + my $msg_hash; + my $host_name; + my $host_key; + +# # check wether incoming msg is a new msg + $host_name = $server_address; + $host_key = $server_passwd; + &main::daemon_log("ServerPackage: host_name: $host_name", 7); + &main::daemon_log("ServerPackage: host_key: $host_key", 7); + eval{ + my $key_cipher = &create_ciphering($host_key); + $msg = &decrypt_msg($crypted_msg, $key_cipher); + $msg_hash = &transform_msg2hash($msg); + }; + if($@) { + &main::daemon_log("ServerPackage: deciphering raise error", 7); + &main::daemon_log("$@", 8); + $msg = undef; + $msg_hash = undef; + $host_name = undef; + $host_key = undef; + } + +# # check wether incoming msg is from a known_server +# if( not defined $msg ) { +# my $query_res = $main::known_server_db->select_dbentry( {table=>'known_server'} ); +# while( my ($hit_num, $hit) = each %{ $query_res } ) { +# $host_name = $hit->{hostname}; +# if( not $host_name =~ "^$host") { +# next; +# } +# $host_key = $hit->{hostkey}; +# &main::daemon_log("ServerPackage: host_name: $host_name", 7); +# &main::daemon_log("ServerPackage: host_key: $host_key", 7); +# eval{ +# my $key_cipher = &create_ciphering($host_key); +# $msg = &decrypt_msg($crypted_msg, $key_cipher); +# $msg_hash = &transform_msg2hash($msg); +# }; +# if($@) { +# &main::daemon_log("ServerPackage: deciphering raise error", 7); +# &main::daemon_log("$@", 8); +# $msg = undef; +# $msg_hash = undef; +# $host_name = undef; +# $host_key = undef; +# } else { +# last; +# } +# } +# } + + # check wether incoming msg is from a known_client + if( not defined $msg ) { + my $query_res = $main::known_clients_db->select_dbentry( {table=>'known_clients'} ); + while( my ($hit_num, $hit) = each %{ $query_res } ) { + $host_name = $hit->{hostname}; + if( not $host_name =~ "^$host") { + next; + } + $host_key = $hit->{hostkey}; + &main::daemon_log("ServerPackage: host_name: $host_name", 7); + &main::daemon_log("ServerPackage: host_key: $host_key", 7); + eval{ + my $key_cipher = &create_ciphering($host_key); + $msg = &decrypt_msg($crypted_msg, $key_cipher); + $msg_hash = &transform_msg2hash($msg); + }; + if($@) { + &main::daemon_log("ServerPackage: deciphering raise error", 7); + &main::daemon_log("$@", 8); + $msg = undef; + $msg_hash = undef; + $host_name = undef; + $host_key = undef; + } else { + last; + } + } + } + + if( not defined $msg ) { + &main::daemon_log("WARNING: ServerPackage do not understand the message:", 5); + &main::daemon_log("$@", 7); + return; + } + + # process incoming msg + my $header = @{$msg_hash->{header}}[0]; + my $source = @{$msg_hash->{source}}[0]; + +# &main::daemon_log("recieve '$header' at ServerPackages from $host", 1); + &main::daemon_log("ServerPackages: msg to process: \n$msg", 5); + +# my @targets = @{$msg_hash->{target}}; +# my $len_targets = @targets; +# if ($len_targets == 0){ +# &main::daemon_log("ERROR: ServerPackages: no target specified for msg $header", 1); +# +# } elsif ($len_targets == 1){ +# # we have only one target symbol +# my $target = $targets[0]; +# &main::daemon_log("SeverPackages: msg is for: $target", 7); +# +# # msg is for server +# if ($header eq 'new_passwd'){ &new_passwd($msg_hash)} +# elsif ($header eq 'here_i_am') { &here_i_am($msg_hash)} +# elsif ($header eq 'who_has') { &who_has($msg_hash) } +# elsif ($header eq 'who_has_i_do') { &who_has_i_do($msg_hash)} +# elsif ($header eq 'update_status') { &update_status($msg_hash) } +# elsif ($header eq 'got_ping') { &got_ping($msg_hash)} +# elsif ($header eq 'get_load') { &execute_actions($msg_hash)} +# else { +# if ($target eq "*") { +# # msg is for all clients +# my $query_res = $main::known_clients_db->select_dbentry( {table=>'known_clients'} ); +# while( my ($hit_num, $hit) = each %{ $query_res } ) { +# $host_name = $hit->{hostname}; +# $host_key = $hit->{hostkey}; +# $msg_hash->{target} = [$host_name]; +# &send_msg_hash2address($msg_hash, $host_name, $host_key); +# } +# +# } else { +# # msg is for one host +# my $host_key; +# +# +# if( not defined $host_key ) { +# my $query_res = $main::known_clients_db->select_dbentry( {table=>'known_clients', hostname=>$target} ); +# if( 1 == keys %{$query_res} ) { +# $host_key = $query_res->{1}->{host_key}; +# } +# } +# +# if( not defined $host_key ) { +# my $query_res = $main::known_server_db->select_dbentry( {table=>'known_server', hostname=>$target} ); +# if( 1 == keys %{$query_res} ) { +# $host_key = $query_res->{1}->{host_key}; +# } +# } +# +# if( not defined $host_key ) { +# &main::daemon_log("ERROR: ServerPackages: target '".$target. +# "' is not known neither in known_clients nor in known_server",1); +# } else { +# &send_msg_hash2address($msg_hash, $target, $host_key); +# } +# } +# } +# +# } elsif ($len_targets > 1 ) { +# # we have more than one target +# # TODO to be implemented +# } + + return ; +} + + +#=== FUNCTION ================================================================ +# NAME: got_ping +# PARAMETERS: msg_hash - hash - hash from function create_xml_hash +# RETURNS: nothing +# DESCRIPTION: process this incoming message +#=============================================================================== +sub got_ping { + my ($msg_hash) = @_; + + my $source = @{$msg_hash->{source}}[0]; + my $target = @{$msg_hash->{target}}[0]; + my $header = @{$msg_hash->{header}}[0]; + + if(exists $main::known_daemons->{$source}) { + &main::add_content2known_daemons(hostname=>$source, status=>$header); + } else { + &main::add_content2known_clients(hostname=>$source, status=>$header); + } + + return; +} + + +#=== FUNCTION ================================================================ +# NAME: new_passwd +# PARAMETERS: msg_hash - ref - hash from function create_xml_hash +# RETURNS: nothing +# DESCRIPTION: process this incoming message +#=============================================================================== +sub new_passwd { + my ($msg_hash) = @_; + + my $header = @{$msg_hash->{header}}[0]; + my $source_name = @{$msg_hash->{source}}[0]; + my $source_key = @{$msg_hash->{new_passwd}}[0]; + my $query_res; + + # check known_clients_db + $query_res = $main::known_clients_db->select_dbentry( {table=>'known_clients', hostname=>$source_name} ); + if( 1 == keys %{$query_res} ) { + my $update_hash = { table=>'known_clients' }; + $update_hash->{where} = [ { hostname=>[$source_name] } ]; + $update_hash->{update} = [ { + hostkey=>[$source_key], + timestamp=>[&get_time], + } ]; + my $res = $main::known_clients_db->update_dbentry( $update_hash ); + + my $hash = &create_xml_hash("confirm_new_passwd", $server_address, $source_name); + &send_msg_hash2address($hash, $source_name, $source_key); + return; + } + + # check known_server_db + $query_res = $main::known_server_db->select_dbentry( {table=>'known_server', hostname=>$source_name } ); + if( 1 == keys %{$query_res} ) { + my $update_hash = { table=>'known_server' }; + $update_hash->{where} = [ { hostname=>[$source_name] } ]; + $update_hash->{update} = [ { + hostkey=>[$source_key], + timestamp=>[&get_time], + } ]; + my $res = $main::known_server_db->update_dbentry( $update_hash ); + + my $hash = &create_xml_hash("confirm_new_passwd", $server_address, $source_name); + &send_msg_hash2address($hash, $source_name, $source_key); + return; + } + + &main::daemon_log("ERROR: $source_name not known for '$header'-msg", 1); + return; +} + + +sub send_msg_hash { + my ($hash, $host_name, $host_key); + + + my $answer = &send_msg_hash2address($hash, $host_name, $host_key); + + return; +} + + +#=== FUNCTION ================================================================ +# NAME: here_i_am +# PARAMETERS: msg_hash - hash - hash from function create_xml_hash +# RETURNS: nothing +# DESCRIPTION: process this incoming message +#=============================================================================== +sub here_i_am { + my ($msg_hash) = @_; + + my $source = @{$msg_hash->{source}}[0]; + my $mac_address = @{$msg_hash->{mac_address}}[0]; + my $out_hash; + + # number of known clients + my $nu_clients = keys %{ $main::known_clients_db->select_dbentry( {table=>'known_clients'} ) }; + + # check wether client address or mac address is already known + if (exists $main::known_clients->{$source}) { + &main::daemon_log("WARNING: $source is already known as a client", 1); + &main::daemon_log("WARNING: values for $source are being overwritten", 1); + $nu_clients --; + } + + # number of actual activ clients + my $act_nu_clients = $nu_clients; + + &main::daemon_log("number of actual activ clients: $act_nu_clients", 5); + &main::daemon_log("number of maximal allowed clients: $max_clients", 5); + + if($max_clients <= $act_nu_clients) { + my $out_hash = &create_xml_hash("denied", $server_address, $source); + &add_content2xml_hash($out_hash, "denied", "I_cannot_take_any_more_clients!"); + my $passwd = @{$msg_hash->{new_passwd}}[0]; + &send_msg_hash2address($out_hash, $source, $passwd); + return; + } + + # new client accepted + my $new_passwd = @{$msg_hash->{new_passwd}}[0]; + + # create entry in known_clients + my $events = @{$msg_hash->{events}}[0]; + + # add entry to known_clients_db + my $res = $main::known_clients_db->add_dbentry( {table=>'known_clients', + primkey=>'hostname', + hostname=>$source, + events=>$events, + macaddress=>$mac_address, + status=>'registered', + hostkey=>$new_passwd, + timestamp=>&get_time, + } ); + + if ($res != 0) { + &main::daemon_log("ERROR: cannot add entry to known_clients: $res"); + return; + } + + # return acknowledgement to client + $out_hash = &create_xml_hash("registered", $server_address, $source); + &send_msg_hash2address($out_hash, $source, $new_passwd); + + # notify registered client to bus + if( $bus_activ eq "on") { + # fetch actual bus key + my $query_res = $main::known_server_db->select_dbentry( {table=>'known_server'} ); + my $hostkey = $query_res->{1}->{hostkey}; + + # send update msg to bus + $out_hash = &create_xml_hash("new_client", $server_address, $bus_address, $source); + &send_msg_hash2address($out_hash, $bus_address, $hostkey); + + &main::daemon_log("send bus msg that client '$source' has registerd at server '$server_address'", 3); + } + + # give the new client his ldap config + &new_ldap_config($source); + + return; +} + + +#=== FUNCTION ================================================================ +# NAME: who_has +# PARAMETERS: msg_hash - hash - hash from function create_xml_hash +# RETURNS: nothing +# DESCRIPTION: process this incoming message +#=============================================================================== +sub who_has { + my ($msg_hash) = @_ ; + + # what is your search pattern + my $search_pattern = @{$msg_hash->{who_has}}[0]; + my $search_element = @{$msg_hash->{$search_pattern}}[0]; + &main::daemon_log("who_has-msg looking for $search_pattern $search_element", 7); + + # scanning known_clients for search_pattern + my @host_addresses = keys %$main::known_clients; + my $known_clients_entries = length @host_addresses; + my $host_address; + foreach my $host (@host_addresses) { + my $client_element = $main::known_clients->{$host}->{$search_pattern}; + if ($search_element eq $client_element) { + $host_address = $host; + last; + } + } + + # search was successful + if (defined $host_address) { + my $source = @{$msg_hash->{source}}[0]; + my $out_msg = &create_xml_hash("who_has_i_do", $server_address, $source, "mac_address"); + &add_content2xml_hash($out_msg, "mac_address", $search_element); + &send_msg_hash2address($out_msg, $bus_address); + } + return; +} + + +sub who_has_i_do { + my ($msg_hash) = @_ ; + my $header = @{$msg_hash->{header}}[0]; + my $source = @{$msg_hash->{source}}[0]; + my $search_param = @{$msg_hash->{$header}}[0]; + my $search_value = @{$msg_hash->{$search_param}}[0]; + print "\ngot msg $header:\nserver $source has client with $search_param $search_value\n"; +} + + +#=== FUNCTION ================================================================ +# NAME: new_ldap_config +# PARAMETERS: address - string - ip address and port of a host +# RETURNS: nothing +# DESCRIPTION: send to address the ldap configuration found for dn gotoLdapServer +#=============================================================================== +sub new_ldap_config { + my ($address) = @_ ; + + my $res = $main::known_clients_db->select_dbentry( { table=>'known_clients', hostname=>$address } ); + + # check hit + my $hit_counter = keys %{$res}; + if( not $hit_counter == 1 ) { + &main::daemon_log("ERROR: more or no hit found in known_clients_db by query by '$address'", 1); + } + + my $macaddress = $res->{1}->{macaddress}; + my $hostkey = $res->{1}->{hostkey}; + + if (not defined $macaddress) { + &main::daemon_log("ERROR: no mac address found for client $address", 1); + return; + } + + # Build LDAP connection + my $ldap = Net::LDAP->new($ldap_uri); + if( not defined $ldap ) { + &main::daemon_log("ERROR: cannot connect to ldap: $ldap_uri", 1); + return; + } + + + # Bind to a directory with dn and password + my $mesg= $ldap->bind($ldap_admin_dn, $ldap_admin_password); + + # Perform search + $mesg = $ldap->search( base => $ldap_base, + scope => 'sub', + attrs => ['dn', 'gotoLdapServer'], + filter => "(&(objectClass=GOhard)(macaddress=$macaddress))"); + $mesg->code && die $mesg->error; + + # Sanity check + if ($mesg->count != 1) { + &main::daemon_log("WARNING: client mac address $macaddress not found/not unique in ldap search", 1); + &main::daemon_log("\tbase: $ldap_base", 1); + &main::daemon_log("\tscope: sub", 1); + &main::daemon_log("\tattrs: dn, gotoLdapServer", 1); + &main::daemon_log("\tfilter: (&(objectClass=GOhard)(macaddress=$macaddress))", 1); + return; + } + + my $entry= $mesg->entry(0); + my $dn= $entry->dn; + my @servers= $entry->get_value("gotoLdapServer"); + my @ldap_uris; + my $server; + my $base; + + # Do we need to look at an object class? + if ($#servers < 1){ + $mesg = $ldap->search( base => $ldap_base, + scope => 'sub', + attrs => ['dn', 'gotoLdapServer'], + filter => "(&(objectClass=gosaGroupOfNames)(member=$dn))"); + $mesg->code && die $mesg->error; + + # Sanity check + if ($mesg->count != 1) { + &main::daemon_log("WARNING: no LDAP information found for client mac $macaddress", 1); + return; + } + + $entry= $mesg->entry(0); + $dn= $entry->dn; + @servers= $entry->get_value("gotoLdapServer"); + } + + @servers= sort (@servers); + + foreach $server (@servers){ + $base= $server; + $server =~ s%^[^:]+:[^:]+:(ldap.*://[^/]+)/.*$%$1%; + $base =~ s%^[^:]+:[^:]+:ldap.*://[^/]+/(.*)$%$1%; + push (@ldap_uris, $server); + } + + # Unbind + $mesg = $ldap->unbind; + + # Assemble data package + my %data = ( 'ldap_uri' => \@ldap_uris, 'ldap_base' => $base, + 'ldap_cfg' => \@ldap_cfg, 'pam_cfg' => \@pam_cfg,'nss_cfg' => \@nss_cfg ); + + # Need to append GOto settings? + if (defined $goto_admin and defined $goto_secret){ + $data{'goto_admin'}= $goto_admin; + $data{'goto_secret'}= $goto_secret; + } + + # Send information + send_msg("new_ldap_config", $server_address, $address, \%data, $hostkey); + + return; +} + + +#=== FUNCTION ================================================================ +# NAME: execute_actions +# PARAMETERS: msg_hash - hash - hash from function create_xml_hash +# RETURNS: nothing +# DESCRIPTION: invokes the script specified in msg_hash which is located under +# /etc/gosad/actions +#=============================================================================== +sub execute_actions { + my ($msg_hash) = @_ ; + my $configdir= '/etc/gosad/actions/'; + my $result; + + my $header = @{$msg_hash->{header}}[0]; + my $source = @{$msg_hash->{source}}[0]; + my $target = @{$msg_hash->{target}}[0]; + + if((not defined $source) + && (not defined $target) + && (not defined $header)) { + &main::daemon_log("ERROR: Entries missing in XML msg for gosad actions under /etc/gosad/actions"); + } else { + my $parameters=""; + my @params = @{$msg_hash->{$header}}; + my $params = join(", ", @params); + &main::daemon_log("execute_actions: got parameters: $params", 5); + + if (@params) { + foreach my $param (@params) { + my $param_value = (&get_content_from_xml_hash($msg_hash, $param))[0]; + &main::daemon_log("execute_actions: parameter -> value: $param -> $param_value", 7); + $parameters.= " ".$param_value; + } + } + + my $cmd= $configdir.$header."$parameters"; + &main::daemon_log("execute_actions: executing cmd: $cmd", 7); + $result= ""; + open(PIPE, "$cmd 2>&1 |"); + while() { + $result.=$_; + } + close(PIPE); + } + + # process the event result + + + return; +} + + +1; diff --git a/gosa-si-poe/modules/TestModule.pm b/gosa-si-poe/modules/TestModule.pm new file mode 100644 index 000000000..90c8d6abb --- /dev/null +++ b/gosa-si-poe/modules/TestModule.pm @@ -0,0 +1,76 @@ +package TestModule; + +use Exporter; +@ISA = ("Exporter"); + +use strict; +use warnings; +use GosaSupportDaemon; + +BEGIN{ +} + +END{} + +### START ########## + + +sub get_module_tags { + + # lese config file aus dort gibt es eine section Basic + # dort stehen drei packettypen, für die sich das modul anmelden kann, gosa-admin-packages, + # server-packages, client-packages + my %tag_hash = (gosa_admin_packages => "yes", + server_packages => "yes", + client_packages => "yes", + ); + return \%tag_hash; +} + + +sub process_incoming_msg { + my ($crypted_msg) = @_ ; + if(not defined $crypted_msg) { + &main::daemon_log("function 'process_incoming_msg': got no msg", 7); + } + &main::daemon_log("TestModule: crypted_msg:$crypted_msg", 7); + &main::daemon_log("TestModule: crypted_msg len:".length($crypted_msg), 7); + + + # chomp address from host who send the message + $crypted_msg =~ /^([\s\S]*?)\.(\d{1,3}?)\.(\d{1,3}?)\.(\d{1,3}?)\.(\d{1,3}?)$/; + $crypted_msg = $1; + my $host = sprintf("%s.%s.%s.%s", $2, $3, $4, $5); + + my $gosa_passwd = $main::gosa_passwd; + my $gosa_cipher = &create_ciphering($gosa_passwd); + + my $in_msg; + my $in_hash; + eval{ + $in_msg = &decrypt_msg($crypted_msg, $gosa_cipher); + $in_hash = &transform_msg2hash($in_msg); + }; + if ($@) { + &main::daemon_log("TestModul konnte msg nicht entschlüsseln:", 5); + &main::daemon_log("$@", 7); + return; + } + + my $header = @{$in_hash->{header}}[0]; + my $ip_address = @{$in_hash->{target}}[0]; + + + # hier kommt die logik suche den entsprechenden daemon, der den client target hat + + my $out_hash = &create_xml_hash("halt", $main::server_address, $ip_address); + + &send_msg_hash2address($out_hash, $ip_address); + + &main::daemon_log("TestModul: ip $ip_address bekommt $header "); + return ; +} + + + + diff --git a/gosa-si-poe/server.conf b/gosa-si-poe/server.conf new file mode 100644 index 000000000..3386c00a6 --- /dev/null +++ b/gosa-si-poe/server.conf @@ -0,0 +1,32 @@ +[general] +log_file = /var/log/gosa-si-server.log +pid_file = /var/run/gosa-si-server.pid +child_max = 10 +child_min = 2 +child_timeout = 10 +job_queue_timeout = 5 + +[bus] +bus_activ = on +bus_passwd = secret-bus-password +bus_ip = 127.0.0.1 +bus_port = 20080 + +[server] +server_activ = on +server_port = 20081 +server_passwd = secret-server-password +max_clients = 5 +server_event_dir = /usr/lib/gosa-si/server/events + +[arp] +arp_activ = off +arp_fifo_path = /var/run/gosa-si/arp-notify + +[gosa] +gosa_activ = on +gosa_ip = 127.0.0.1 +gosa_port = 20082 +gosa_passwd = secret-gosa-password +gosa_timeout = 5 + diff --git a/gosa-si-poe/server/events/ping b/gosa-si-poe/server/events/ping new file mode 100755 index 000000000..f8ae99606 --- /dev/null +++ b/gosa-si-poe/server/events/ping @@ -0,0 +1,26 @@ +#!/usr/bin/perl + +use strict; +use warnings; +use GosaSupportDaemon; + + +# transform msg to hash +my $hash = &transform_msg2hash($ARGV[0]); + +# extract from hash all what you need +my $header = @{$hash->{header}}[0]; +my $source = @{$hash->{source}}[0]; +my $target = @{$hash->{target}}[0]; +my $jobdb_id = @{$hash->{jobdb_id}}[0]; + +# and do what ever you want + + +my $out_hash = &create_xml_hash("got_ping", "10.89.1.155:10001", "10.89.1.155:10000"); +if (defined $jobdb_id) { + &add_content2xml_hash($out_hash, 'jobdb_id', $jobdb_id); +} +my $out_xml = &create_xml_string($out_hash); +print $out_xml; + diff --git a/gosa-si-poe/tests/client.php b/gosa-si-poe/tests/client.php new file mode 100755 index 000000000..8c715148d --- /dev/null +++ b/gosa-si-poe/tests/client.php @@ -0,0 +1,43 @@ +#!/usr/bin/php5 -q +setEncryptionKey("secret-gosa-password"); + +if($sock->connected()){ + /* Prepare a hunge bunch of data to be send */ + +# add +#$data = "
gosa_ping
10.89.1.155:2008210.89.1.155:20080
"; +# $data = "
job_ping
10.89.1.155:2008300:1B:77:04:8A:6C 19700101000000
"; +# $data = "
job_sayHello
10.89.1.155:2008300:1B:77:04:8A:6C 20130102133900
"; +# $data = "
job_ping
10.89.1.155:2008300:1B:77:04:8A:6C 20130102133900
"; + +# delete + #$data = "
gosa_delete_jobdb_entry
headertag sayHello
"; + +# update + #$data = "
gosa_update_status_jobdb_entry
waiting processing
"; + #$data = "
gosa_update_status_jobdb_entry
waiting
"; + #$data = "
gosa_update_timestamp_jobdb_entry
20130123456789
"; + +# query +$data = "
gosa_query_jobdb
statuserror
"; + +# clear + #$data = "
gosa_clear_jobdb
"; + + $sock->write($data); + $answer = "nothing"; + $answer = $sock->read(); + echo ">>>$answer<<<\n"; + $sock->close(); +}else{ + echo "... FAILED!\n"; +} + +?> diff --git a/gosa-si-poe/tests/sqlite-check.pl b/gosa-si-poe/tests/sqlite-check.pl new file mode 100755 index 000000000..63f2ac6d8 --- /dev/null +++ b/gosa-si-poe/tests/sqlite-check.pl @@ -0,0 +1,95 @@ +#!/usr/bin/perl +#=============================================================================== +# +# FILE: DBD-SQlite.pl +# +# USAGE: ./DBD-SQlite.pl +# +# DESCRIPTION: +# +# OPTIONS: --- +# REQUIREMENTS: --- +# BUGS: --- +# NOTES: --- +# AUTHOR: (), <> +# COMPANY: +# VERSION: 1.0 +# CREATED: 20.12.2007 08:54:52 CET +# REVISION: --- +#=============================================================================== + +use strict; +use warnings; +use GOSA::DBsqlite; + + +print "START\n"; +my $res; +my $db_name; + +$db_name = "/var/lib/gosa-si/jobs.db"; +if (-e $db_name) { + print "\n############################################################\n"; + $db_name =~ /\/([^\/]*?)\.db$/; + my $table_name = $1; + print "$db_name\n"; + print "$table_name\n"; + my $sqlite = GOSA::DBsqlite->new($db_name); + my $col_names = $sqlite->get_table_columns($table_name); + print join(', ', @{ $col_names } )."\n" ; + my $answer = $sqlite->show_table($table_name); + print $answer."\n"; +} + + +$db_name = "/var/lib/gosa-si/known_clients.db"; +if (-e $db_name) { + print "\n############################################################\n"; + $db_name =~ /\/([^\/]*?)\.db$/; + my $table_name = $1; + + print "$db_name\n"; + print "$table_name\n"; + + my $sqlite = GOSA::DBsqlite->new($db_name); + my $col_names = $sqlite->get_table_columns($table_name); + print join(', ', @{ $col_names } )."\n" ; + my $answer = $sqlite->show_table($table_name); + print $answer."\n"; +} + + +$db_name = "/var/lib/gosa-si/known_server.db"; +if (-e $db_name) { + print "\n############################################################\n"; + $db_name =~ /\/([^\/]*?)\.db$/; + my $table_name = $1; + + print "$db_name\n"; + print "$table_name\n"; + + my $sqlite = GOSA::DBsqlite->new($db_name); + my $col_names = $sqlite->get_table_columns($table_name); + print join(', ', @{ $col_names } )."\n" ; + my $answer = $sqlite->show_table($table_name); + print $answer."\n"; +} + + +$db_name = "/var/lib/gosa-si/bus_known_server.db"; +if (-e $db_name) { + print "\n############################################################\n"; + $db_name =~ /\/([^\/]*?)\.db$/; + my $table_name = $1; + print "$db_name\n"; + print "$table_name\n"; + my $sqlite = GOSA::DBsqlite->new($db_name); + my $col_names = $sqlite->get_table_columns($table_name); + print join(', ', @{ $col_names } )."\n" ; + my $answer = $sqlite->show_table($table_name); + print $answer."\n"; +} + + + +print "\nFINISH\n"; diff --git a/gosa-si-poe/tests/testGOsa.pl b/gosa-si-poe/tests/testGOsa.pl new file mode 100644 index 000000000..9ecb8f385 --- /dev/null +++ b/gosa-si-poe/tests/testGOsa.pl @@ -0,0 +1,107 @@ +#!/usr/bin/perl +#=============================================================================== +# +# FILE: testGosa.pl +# +# USAGE: ./testGosa.pl +# +# DESCRIPTION: +# +# OPTIONS: --- +# REQUIREMENTS: --- +# BUGS: --- +# NOTES: --- +# AUTHOR: (), <> +# COMPANY: +# VERSION: 1.0 +# CREATED: 06.12.2007 14:31:37 CET +# REVISION: --- +#=============================================================================== + +use strict; +use warnings; +use IO::Socket::INET; +use Digest::MD5 qw(md5 md5_hex md5_base64); +use Crypt::Rijndael; +use MIME::Base64; + +sub create_ciphering { + my ($passwd) = @_; + + $passwd = substr(md5_hex("$passwd") x 32, 0, 32); + my $iv = substr(md5_hex('GONICUS GmbH'),0, 16); + print "iv: $iv\n"; + print "key: $passwd\n"; + + my $my_cipher = Crypt::Rijndael->new($passwd ,Crypt::Rijndael::MODE_CBC() ); + $my_cipher->set_iv($iv); + return $my_cipher; +} + +sub decrypt_msg { + my ($crypted_msg, $my_cipher) = @_ ; + $crypted_msg = &decode_base64($crypted_msg); + my $msg = $my_cipher->decrypt($crypted_msg); + return $msg; +} + +sub encrypt_msg { + my ($msg, $my_cipher) = @_; + if(not defined $my_cipher) { print "no cipher object\n"; } + $msg = "\0"x(16-length($msg)%16).$msg; + my $crypted_msg = $my_cipher->encrypt($msg); + chomp($crypted_msg = &encode_base64($crypted_msg)); + return $crypted_msg; +} + + + +my $gosa_server = IO::Socket::INET->new(LocalPort => "9999", + Type => SOCK_STREAM, + Reuse => 1, + Listen => 1, + ); + + + + + +my $client = $gosa_server->accept(); +my $other_end = getpeername($client); +if(not defined $other_end) { + print "client cannot be identified:"; +} else { + my ($port, $iaddr) = unpack_sockaddr_in($other_end); + my $actual_ip = inet_ntoa($iaddr); + print "accept client at gosa socket from $actual_ip\n"; + chomp(my $crypted_msg = <$client>); + print "crypted msg: <<<$crypted_msg<<<\n"; + + my $cipher = &create_ciphering("ferdinand_frost"); + + my $msg = &decrypt_msg($crypted_msg, $cipher); + print "msg: <<<$msg<<<\n"; + + print "\n#################################\n\n"; + + my $answer = "gosa answer: $msg"; + + print "answer: $answer\n"; + + my $out_cipher = &create_ciphering("ferdinand_frost"); + my $crypted_answer = &encrypt_msg($answer, $out_cipher); + + print $client $crypted_answer."\n"; + +} + +sleep(3); +close($client); + + + + + + + + -- 2.30.2