#!/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); # 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); } }