1 #!/usr/bin/perl
2 #===============================================================================
3 #
4 # FILE: gosa-si-bus
5 #
6 # USAGE: ./gosa-si-bus
7 #
8 # DESCRIPTION:
9 #
10 # OPTIONS: ---
11 # REQUIREMENTS: ---
12 # BUGS: ---
13 # NOTES:
14 # AUTHOR: (Andreas Rettenberger), <rettenberger@gonicus.de>
15 # COMPANY:
16 # VERSION: 1.0
17 # CREATED: 12.09.2007 08:54:41 CEST
18 # REVISION: ---
19 #===============================================================================
21 use strict;
22 use warnings;
23 use Getopt::Long;
24 use Config::IniFiles;
25 use POSIX;
26 use Time::HiRes qw( gettimeofday );
28 use POE qw(Component::Server::TCP);
29 use Data::Dumper;
30 use Crypt::Rijndael;
31 use IO::Socket::INET;
32 use NetAddr::IP;
33 use XML::Simple;
34 use MIME::Base64;
35 use File::Basename;
36 use Digest::MD5 qw(md5 md5_hex md5_base64);
38 use GOSA::GosaSupportDaemon;
39 use GOSA::DBsqlite;
41 my ($cfg_file, $default_cfg_file, %cfg_defaults, $foreground, $verbose, $pid_file, $procid, $pid, $log_file,);
42 my ($bus_address, $bus_key, $bus_ip, $bus_port, $bus_mac_address);
43 my ($bus_known_server_db, $bus_known_server_file_name, $bus_known_clients_db, $bus_known_clients_file_name);
44 my $xml;
45 our $prg= basename($0);
47 $foreground = 0 ;
48 %cfg_defaults = (
49 "general" => {
50 "log_file" => [\$log_file, "/var/run/".$prg.".log"],
51 "pid_file" => [\$pid_file, "/var/run/".$prg.".pid"],
52 },
53 "bus" => {
54 "key" => [\$bus_key, "secret-bus-password"],
55 "ip" => [\$bus_ip, "0.0.0.0"],
56 "port" => [\$bus_port, "20080"],
57 "known-servers" => [\$bus_known_server_file_name, "/var/lib/gosa-si/bus-servers.db"],
58 "known-clients" => [\$bus_known_clients_file_name, "/var/lib/gosa-si/bus-clients.db"],
59 },
60 );
62 #=== FUNCTIONS = functions =====================================================
64 #=== FUNCTION ================================================================
65 # NAME: check_cmdline_param
66 # PARAMETERS:
67 # RETURNS:
68 # DESCRIPTION:
69 #===============================================================================
70 sub check_cmdline_param () {
71 my @error_l;
72 my $error = 0;
74 if( !$cfg_file ) {
75 $cfg_file = "/etc/gosa-si/bus.conf";
76 }
77 if( not -f $cfg_file ) {
78 push(@error_l, "can not find file '$cfg_file'");
79 $error++;
80 }
81 if( not -r $cfg_file) {
82 push(@error_l, "can not read file '$cfg_file'");
83 $error++;
84 }
86 if( $error > 0 ) {
87 &usage( "", 1 );
88 print STDERR join("\n", @error_l);
89 print STDERR "\n";
90 exit( -1 );
91 }
92 }
95 #=== FUNCTION ================================================================
96 # NAME: read_configfile
97 # PARAMETERS: cfg_file - string -
98 # RETURNS: nothing
99 # DESCRIPTION: read cfg_file and set variables
100 #===============================================================================
101 sub read_configfile {
102 my $cfg;
103 if( defined( $cfg_file) && ( length($cfg_file) > 0 )) {
104 if( -r $cfg_file ) {
105 $cfg = Config::IniFiles->new( -file => $cfg_file );
106 } else {
107 print STDERR "Couldn't read config file!";
108 }
109 } else {
110 $cfg = Config::IniFiles->new() ;
111 }
112 foreach my $section (keys %cfg_defaults) {
113 foreach my $param (keys %{$cfg_defaults{ $section }}) {
114 my $pinfo = $cfg_defaults{ $section }{ $param };
115 ${@$pinfo[ 0 ]} = $cfg->val( $section, $param, @$pinfo[ 1 ] );
116 }
117 }
118 }
121 #=== FUNCTION ================================================================
122 # NAME: check_pid
123 # PARAMETERS: nothing
124 # RETURNS: nothing
125 # DESCRIPTION: handels pid processing
126 #===============================================================================
127 sub check_pid {
128 $pid = -1;
129 # Check, if we are already running
130 if( open(LOCK_FILE, "<$pid_file") ) {
131 $pid = <LOCK_FILE>;
132 if( defined $pid ) {
133 chomp( $pid );
134 if( -f "/proc/$pid/stat" ) {
135 my($stat) = `cat /proc/$pid/stat` =~ m/$pid \((.+)\).*/;
136 if( $0 eq $stat ) {
137 close( LOCK_FILE );
138 exit -1;
139 }
140 }
141 }
142 close( LOCK_FILE );
143 unlink( $pid_file );
144 }
146 # create a syslog msg if it is not to possible to open PID file
147 if (not sysopen(LOCK_FILE, $pid_file, O_WRONLY|O_CREAT|O_EXCL, 0644)) {
148 my($msg) = "Couldn't obtain lockfile '$pid_file' ";
149 if (open(LOCK_FILE, '<', $pid_file)
150 && ($pid = <LOCK_FILE>))
151 {
152 chomp($pid);
153 $msg .= "(PID $pid)\n";
154 } else {
155 $msg .= "(unable to read PID)\n";
156 }
157 if( ! ($foreground) ) {
158 openlog( $0, "cons,pid", "daemon" );
159 syslog( "warning", $msg );
160 closelog();
161 }
162 else {
163 print( STDERR " $msg " );
164 }
165 exit( -1 );
166 }
167 }
170 #=== FUNCTION ================================================================
171 # NAME: usage
172 # PARAMETERS: nothing
173 # RETURNS: nothing
174 # DESCRIPTION: print out usage text to STDERR
175 #===============================================================================
176 sub usage {
177 print STDERR << "EOF" ;
178 usage: $prg [-hvf] [-c config]
180 -h : this (help) message
181 -c <file> : config file
182 -f : foreground, process will not be forked to background
183 -v : be verbose (multiple to increase verbosity)
184 EOF
185 print "\n" ;
186 }
189 #=== FUNCTION ================================================================
190 # NAME: logging
191 # PARAMETERS: level - string - default 'info'
192 # msg - string -
193 # facility - string - default 'LOG_DAEMON'
194 # RETURNS:
195 # DESCRIPTION:
196 #===============================================================================
197 sub daemon_log {
198 # log into log_file
199 my( $msg, $level ) = @_;
200 if(not defined $msg) { return }
201 if(not defined $level) { $level = 1 }
202 if(defined $log_file){
203 open(LOG_HANDLE, ">>$log_file");
204 if(not defined open( LOG_HANDLE, ">>$log_file" )) {
205 print STDERR "cannot open $log_file: $!";
206 return }
207 chomp($msg);
208 if($level <= $verbose){
209 my ($seconds, $minutes, $hours, $monthday, $month,
210 $year, $weekday, $yearday, $sommertime) = localtime(time);
211 $hours = $hours < 10 ? $hours = "0".$hours : $hours;
212 $minutes = $minutes < 10 ? $minutes = "0".$minutes : $minutes;
213 $seconds = $seconds < 10 ? $seconds = "0".$seconds : $seconds;
214 my @monthnames = ("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec");
215 $month = $monthnames[$month];
216 $monthday = $monthday < 10 ? $monthday = "0".$monthday : $monthday;
217 $year+=1900;
219 my $log_msg = "$month $monthday $hours:$minutes:$seconds $prg $msg\n";
220 print LOG_HANDLE $log_msg;
221 if( $foreground ) {
222 print STDERR $log_msg;
223 }
224 }
225 close( LOG_HANDLE );
226 }
227 }
230 #=== FUNCTION ================================================================
231 # NAME: get_ip
232 # PARAMETERS: interface name (i.e. eth0)
233 # RETURNS: (ip address)
234 # DESCRIPTION: Uses ioctl to get ip address directly from system.
235 #===============================================================================
236 sub get_ip {
237 my $ifreq= shift;
238 my $result= "";
239 my $SIOCGIFADDR= 0x8915; # man 2 ioctl_list
240 my $proto= getprotobyname('ip');
242 socket SOCKET, PF_INET, SOCK_DGRAM, $proto
243 or die "socket: $!";
245 if(ioctl SOCKET, $SIOCGIFADDR, $ifreq) {
246 my ($if, $sin) = unpack 'a16 a16', $ifreq;
247 my ($port, $addr) = sockaddr_in $sin;
248 my $ip = inet_ntoa $addr;
250 if ($ip && length($ip) > 0) {
251 $result = $ip;
252 }
253 }
255 return $result;
256 }
259 #=== FUNCTION ================================================================
260 # NAME: get_interface_for_ip
261 # PARAMETERS: ip address (i.e. 192.168.0.1)
262 # RETURNS: array: list of interfaces if ip=0.0.0.0, matching interface if found, undef else
263 # DESCRIPTION: Uses proc fs (/proc/net/dev) to get list of interfaces.
264 #===============================================================================
265 sub get_interface_for_ip {
266 my $result;
267 my $ip= shift;
268 if ($ip && length($ip) > 0) {
269 my @ifs= &get_interfaces();
270 if($ip eq "0.0.0.0") {
271 $result = "all";
272 } else {
273 foreach (@ifs) {
274 my $if=$_;
275 if(get_ip($if) eq $ip) {
276 $result = $if;
277 last;
278 }
279 }
280 }
281 }
282 return $result;
283 }
286 #=== FUNCTION ================================================================
287 # NAME: get_interfaces
288 # PARAMETERS: none
289 # RETURNS: (list of interfaces)
290 # DESCRIPTION: Uses proc fs (/proc/net/dev) to get list of interfaces.
291 #===============================================================================
292 sub get_interfaces {
293 my @result;
294 my $PROC_NET_DEV= ('/proc/net/dev');
296 open(PROC_NET_DEV, "<$PROC_NET_DEV")
297 or die "Could not open $PROC_NET_DEV";
299 my @ifs = <PROC_NET_DEV>;
301 close(PROC_NET_DEV);
303 # Eat first two line
304 shift @ifs;
305 shift @ifs;
307 chomp @ifs;
308 foreach my $line(@ifs) {
309 my $if= (split /:/, $line)[0];
310 $if =~ s/^\s+//;
311 push @result, $if;
312 }
314 return @result;
315 }
318 #=== FUNCTION ================================================================
319 # NAME: get_mac
320 # PARAMETERS: interface name (i.e. eth0)
321 # RETURNS: (mac address)
322 # DESCRIPTION: Uses ioctl to get mac address directly from system.
323 #===============================================================================
324 sub get_mac {
325 my $ifreq= shift;
326 my $result;
327 if ($ifreq && length($ifreq) > 0) {
328 if($ifreq eq "all") {
329 if(defined($bus_ip)) {
330 $result = &get_local_mac_for_remote_ip($bus_ip);
331 }
332 elsif ($bus_mac_address && length($bus_mac_address) > 0 && !($bus_mac_address eq "00:00:00:00:00:00")){
333 $result = &client_mac_address;
334 }
335 else {
336 $result = "00:00:00:00:00:00";
337 }
338 } else {
339 my $SIOCGIFHWADDR= 0x8927; # man 2 ioctl_list
341 # A configured MAC Address should always override a guessed value
342 if ($bus_mac_address and length($bus_mac_address) > 0 and not($bus_mac_address eq "00:00:00:00:00:00")) {
343 $result= $bus_mac_address;
344 }
345 else {
346 socket SOCKET, PF_INET, SOCK_DGRAM, getprotobyname('ip')
347 or die "socket: $!";
349 if(ioctl SOCKET, $SIOCGIFHWADDR, $ifreq) {
350 my ($if, $mac)= unpack 'h36 H12', $ifreq;
352 if (length($mac) > 0) {
353 $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])$/;
354 $mac= sprintf("%s:%s:%s:%s:%s:%s", $1, $2, $3, $4, $5, $6);
355 $result = $mac;
356 }
357 }
358 }
359 }
360 }
361 return $result;
362 }
365 #=== FUNCTION ================================================================
366 # NAME: get_local_mac_for_remote_ip
367 # PARAMETERS: none (takes server_ip from global variable)
368 # RETURNS: (ip address from interface that is used for communication)
369 # DESCRIPTION: Uses ioctl to get routing table from system, checks which entry
370 # matches (defaultroute last).
371 #===============================================================================
372 sub get_local_mac_for_remote_ip {
373 my $server_ip= shift;
374 my $result= "00:00:00:00:00:00";
376 if($server_ip =~ /^(\d\d?\d?\.){3}\d\d?\d?$/) {
377 my $PROC_NET_ROUTE= ('/proc/net/route');
379 open(PROC_NET_ROUTE, "<$PROC_NET_ROUTE")
380 or die "Could not open $PROC_NET_ROUTE";
382 my @ifs = <PROC_NET_ROUTE>;
384 close(PROC_NET_ROUTE);
386 # Eat header line
387 shift @ifs;
388 chomp @ifs;
389 foreach my $line(@ifs) {
390 my ($Iface,$Destination,$Gateway,$Flags,$RefCnt,$Use,$Metric,$Mask,$MTU,$Window,$IRTT)=split(/\s/, $line);
391 my $destination;
392 my $mask;
393 my ($d,$c,$b,$a)=unpack('a2 a2 a2 a2', $Destination);
394 $destination= sprintf("%d.%d.%d.%d", hex($a), hex($b), hex($c), hex($d));
395 ($d,$c,$b,$a)=unpack('a2 a2 a2 a2', $Mask);
396 $mask= sprintf("%d.%d.%d.%d", hex($a), hex($b), hex($c), hex($d));
397 if(new NetAddr::IP($server_ip)->within(new NetAddr::IP($destination, $mask))) {
398 # destination matches route, save mac and exit
399 $result= &get_mac($Iface);
400 last;
401 }
402 }
403 } else {
404 daemon_log("get_local_mac_for_remote_ip was called with a non-ip parameter: $server_ip", 1);
405 }
406 return $result;
407 }
409 sub bus_matches {
410 my $target = shift;
411 my $target_ip = sprintf("%s", $target =~ /^([0-9\.]*?):.*$/);
412 my $result = 0;
414 if($bus_ip eq $target_ip) {
415 $result= 1;
416 } elsif ($bus_ip eq "0.0.0.0") {
417 if ($target_ip eq "127.0.0.1") {
418 $result= 1;
419 } else {
420 my $PROC_NET_ROUTE= ('/proc/net/route');
422 open(PROC_NET_ROUTE, "<$PROC_NET_ROUTE")
423 or die "Could not open $PROC_NET_ROUTE";
425 my @ifs = <PROC_NET_ROUTE>;
427 close(PROC_NET_ROUTE);
429 # Eat header line
430 shift @ifs;
431 chomp @ifs;
432 foreach my $line(@ifs) {
433 my ($Iface,$Destination,$Gateway,$Flags,$RefCnt,$Use,$Metric,$Mask,$MTU,$Window,$IRTT)=split(/\s/, $line);
434 my $destination;
435 my $mask;
436 my ($d,$c,$b,$a)=unpack('a2 a2 a2 a2', $Destination);
437 $destination= sprintf("%d.%d.%d.%d", hex($a), hex($b), hex($c), hex($d));
438 ($d,$c,$b,$a)=unpack('a2 a2 a2 a2', $Mask);
439 $mask= sprintf("%d.%d.%d.%d", hex($a), hex($b), hex($c), hex($d));
440 if(new NetAddr::IP($target_ip)->within(new NetAddr::IP($destination, $mask))) {
441 # destination matches route, save mac and exit
442 $result= 1;
443 last;
444 }
445 }
446 }
447 } else {
448 &main::daemon_log("Target ip $target_ip does not match bus ip $bus_ip",1);
449 }
451 return $result;
452 }
454 #=== FUNCTION ================================================================
455 # NAME: create_passwd
456 # PARAMETERS: nothing
457 # RETURNS: new_passwd - string
458 # DESCRIPTION: creates a 32 bit long random passwd out of "a".."z","A".."Z",0..9
459 #===============================================================================
460 sub create_passwd {
461 my $new_passwd = "";
462 for(my $i=0; $i<31; $i++) {
463 $new_passwd .= ("a".."z","A".."Z",0..9)[int(rand(62))]
464 }
465 return $new_passwd;
466 }
469 sub create_ciphering {
470 my ($passwd) = @_;
471 if((!defined($passwd)) || length($passwd)==0) {
472 $passwd = "";
473 }
474 $passwd = substr(md5_hex("$passwd") x 32, 0, 32);
475 my $iv = substr(md5_hex('GONICUS GmbH'),0, 16);
476 my $my_cipher = Crypt::Rijndael->new($passwd , Crypt::Rijndael::MODE_CBC());
477 $my_cipher->set_iv($iv);
478 return $my_cipher;
479 }
482 sub encrypt_msg {
483 my ($msg, $key) = @_;
484 my $my_cipher = &create_ciphering($key);
485 my $len;
486 {
487 use bytes;
488 $len= 16-length($msg)%16;
489 }
490 $msg = "\0"x($len).$msg;
491 $msg = $my_cipher->encrypt($msg);
492 chomp($msg = &encode_base64($msg));
493 # there are no newlines allowed inside msg
494 $msg=~ s/\n//g;
495 return $msg;
496 }
499 sub decrypt_msg {
501 my ($msg, $key) = @_ ;
502 $msg = &decode_base64($msg);
503 my $my_cipher = &create_ciphering($key);
504 $msg = $my_cipher->decrypt($msg);
505 $msg =~ s/\0*//g;
506 return $msg;
507 }
510 sub send_msg_hash2address {
511 my ($msg_hash, $address, $encrypt_key) = @_ ;
512 my $msg = &create_xml_string($msg_hash);
513 my $header = @{$msg_hash->{'header'}}[0];
514 &send_msg_to_target($msg, $address, $encrypt_key, $header);
516 return;
517 }
520 sub send_msg_to_target {
521 my ($msg, $address, $encrypt_key, $msg_header) = @_ ;
522 my $error = 0;
523 my $header;
524 my $new_status;
525 my $act_status;
526 my ($sql_statement, $res);
528 if( $msg_header ) {
529 $header = "'$msg_header'-";
530 }
531 else {
532 $header = "";
533 }
535 # encrypt xml msg
536 my $crypted_msg = &encrypt_msg($msg, $encrypt_key);
538 # opensocket
539 my $socket = &open_socket($address);
540 if( !$socket ) {
541 daemon_log("cannot send ".$header."msg to $address , host not reachable", 1);
542 $error++;
543 }
545 if( $error == 0 ) {
546 # send xml msg
547 print $socket $crypted_msg."\n";
549 daemon_log("send ".$header."msg to $address", 1);
550 daemon_log("message:\n$msg", 8);
552 }
554 # close socket in any case
555 if( $socket ) {
556 close $socket;
557 }
559 if( $error > 0 ) { $new_status = "down"; }
560 else { $new_status = $msg_header; }
563 # known_clients
564 $sql_statement = "SELECT * FROM bus_known_clients WHERE hostname='$address'";
565 $res = $bus_known_clients_db->select_dbentry($sql_statement);
566 if( keys(%$res) > 0 ) {
567 $act_status = $res->{1}->{'status'};
568 if( $act_status eq "down" ) {
569 $sql_statement = "DELETE FROM bus_known_clients WHERE hostname='$address'";
570 $res = $bus_known_clients_db->del_dbentry($sql_statement);
571 daemon_log("WARNING: failed 2x to send msg to host '$address', delete host from bus_known_clients", 3);
572 }
573 else {
574 $sql_statement = "UPDATE bus_known_clients SET status='$new_status' WHERE hostname='$address'";
575 $res = $bus_known_clients_db->update_dbentry($sql_statement);
576 daemon_log("INFO: set '$address' from status '$act_status' to '$new_status'", 5);
577 }
578 }
580 # known_server
581 $sql_statement = "SELECT * FROM bus_known_server WHERE hostname='$address'";
582 $res = $bus_known_server_db->select_dbentry($sql_statement);
583 if( keys(%$res) > 0) {
584 $act_status = $res->{1}->{'status'};
585 if( $act_status eq "down" ) {
586 $sql_statement = "DELETE FROM bus_known_clients WHERE hostname='$address'";
587 $res = $bus_known_clients_db->del_dbentry($sql_statement);
588 daemon_log("WARNING: failed 2x to a send msg to host '$address', delete host from bus_known_server", 3);
589 }
590 else {
591 $sql_statement = "UPDATE bus_known_server SET status='$new_status' WHERE hostname='$address'";
592 $res = $bus_known_server_db->update_dbentry($sql_statement);
593 daemon_log("INFO: set '$address' from status '$act_status' to '$new_status'", 5)
594 }
595 }
597 return;
598 }
601 #=== FUNCTION ================================================================
602 # NAME: open_socket
603 # PARAMETERS: PeerAddr string something like 192.168.1.1 or 192.168.1.1:10000
604 # [PeerPort] string necessary if port not appended by PeerAddr
605 # RETURNS: socket IO::Socket::INET
606 # DESCRIPTION: open a socket to PeerAddr
607 #===============================================================================
608 sub open_socket {
609 my ($PeerAddr, $PeerPort) = @_ ;
610 if(defined($PeerPort)){
611 $PeerAddr = $PeerAddr.":".$PeerPort;
612 }
613 my $socket;
614 $socket = new IO::Socket::INET(PeerAddr => $PeerAddr,
615 Porto => "tcp",
616 Type => SOCK_STREAM,
617 Timeout => 5,
618 );
619 if(not defined $socket) {
620 return;
621 }
622 &daemon_log("open_socket: $PeerAddr", 7);
623 return $socket;
624 }
627 sub check_key_and_xml_validity {
628 my ($crypted_msg, $module_key) = @_;
629 #print STDERR "crypted_msg:$crypted_msg\n";
630 #print STDERR "modul_key:$module_key\n";
632 my $msg;
633 my $msg_hash;
634 eval{
635 $msg = &decrypt_msg($crypted_msg, $module_key);
636 &main::daemon_log("decrypted_msg: \n$msg", 8);
638 $msg_hash = $xml->XMLin($msg, ForceArray=>1);
640 # check header
641 my $header_l = $msg_hash->{'header'};
642 if( 1 != @{$header_l} ) {
643 die 'no or more headers specified';
644 }
645 my $header = @{$header_l}[0];
646 if( 0 == length $header) {
647 die 'header has length 0';
648 }
650 # check source
651 my $source_l = $msg_hash->{'source'};
652 if( 1 != @{$source_l} ) {
653 die 'no or more sources specified';
654 }
655 my $source = @{$source_l}[0];
656 if( 0 == length $source) {
657 die 'source has length 0';
658 }
660 # check target
661 my $target_l = $msg_hash->{'target'};
662 if( 1 != @{$target_l} ) {
663 die 'no or more targets specified ';
664 }
665 my $target = @{$target_l}[0];
666 if( 0 == length $target) {
667 die 'target has length 0 ';
668 }
670 };
671 if($@) {
672 &main::daemon_log("WARNING: do not understand the message or msg is not gosa-si envelope conform:", 5);
673 &main::daemon_log("$@", 8);
674 }
676 return ($msg, $msg_hash);
677 }
680 sub input_from_new_server {
681 no strict "refs";
682 my ($input) = @_ ;
683 my ($msg, $msg_hash);
685 daemon_log("bus_known_server host_name: new host", 7);
686 daemon_log("bus_known_server host_key: $bus_key", 7);
688 # check if module can open msg envelope with key
689 ($msg, $msg_hash) = &check_key_and_xml_validity($input, $bus_key);
691 if( (!$msg) || (!$msg_hash) ) {
692 daemon_log("Incoming message is not from a new gosa-si-server", 5);
693 }
695 return ($msg, $msg_hash);
696 }
699 sub input_from_known_server {
700 my ($input, $remote_ip) = @_ ;
701 my ($msg, $msg_hash);
703 my $sql_statement= "SELECT * FROM bus_known_server";
704 my $query_res = $bus_known_server_db->select_dbentry( $sql_statement );
706 while( my ($hit_num, $hit) = each %{ $query_res } ) {
707 my $host_name = $hit->{hostname};
708 if( not $host_name =~ "^$remote_ip") {
709 next;
710 }
711 my $host_key = $hit->{hostkey};
712 daemon_log("bus_known_server host_name: $host_name", 7);
713 daemon_log("bus_known_server host_key: $host_key", 7);
715 # check if module can open msg envelope with module key
716 my ($tmp_msg, $tmp_msg_hash) = &check_key_and_xml_validity($input, $host_key);
717 if( (!$tmp_msg) || (!$tmp_msg_hash) ) {
718 next;
719 }
720 else {
721 $msg = $tmp_msg;
722 $msg_hash = $tmp_msg_hash;
723 last;
724 }
725 }
727 if( (!$msg) || (!$msg_hash) ) {
728 daemon_log("Incoming message is not from a known gosa-si-server", 5);
729 }
731 return ($msg, $msg_hash);
732 }
735 sub _start {
736 my $kernel = $_[KERNEL];
737 $kernel->alias_set('gosa_si_bus_session');
738 return;
739 }
741 sub _default {
742 daemon_log("ERROR: can not handle incoming msg with header '$_[ARG0]'", 1);
743 return;
744 }
747 sub bus_input {
748 my ($kernel, $heap, $input, $wheel, $session) = @_[KERNEL, HEAP, ARG0, ARG1, SESSION];
749 my ($msg, $msg_hash);
750 my $error = 0;
752 daemon_log("Incoming msg:\n$input\n", 8);
754 # msg is from a new gosa-si-server
755 ($msg, $msg_hash) = &input_from_new_server($input);
757 # msg is from a gosa-si-server or gosa-si-bus
758 if(( !$msg ) || ( !$msg_hash ) ){
759 ($msg, $msg_hash) = &input_from_known_server($input, $heap->{'remote_ip'});
760 }
762 # an error occurred
763 if(( !$msg ) || ( !$msg_hash )){
764 $error++;
765 }
767 if( $error == 0) {
768 my @target_l = @{$msg_hash->{'target'}};
769 my $source = @{$msg_hash->{'source'}}[0];
770 my $header = @{$msg_hash->{header}}[0];
772 my $target_string = join(",", @target_l);
773 daemon_log("got msg '$header' with target '$target_string' from ".$heap->{'remote_ip'}, 3);
775 if( 1 == length(@target_l) && &bus_matches($target_l[0]) ) {
776 # msg is for bus
777 #print STDERR "msg is for bus\n";
778 $kernel->post('gosa_si_bus_session', $header, $msg, $msg_hash);
779 }
780 else {
781 # msg is for someone else, deliver it
783 #print STDERR "msg is for someone else\n";
784 foreach my $target (@target_l) {
785 if( $target =~ /(\d{0,3}\.\d{0,3}\.\d{0,3}\.\d{0,3}:\d+)/ ) {
786 # target is a ip address
787 my ($sql_statement, $query_res);
789 $sql_statement = "SELECT * FROM bus_known_server WHERE hostname='$target'";
790 $query_res = $bus_known_server_db->select_dbentry( $sql_statement );
791 if( 1 == keys(%$query_res) ) {
792 my $host_name = $query_res->{1}->{'hostname'};
793 my $host_key = $query_res->{1}->{'hostkey'};
794 &send_msg_to_target($msg, $host_name, $host_key, $header);
795 next;
796 }
798 $sql_statement = "SELECT * FROM bus_known_clients WHERE hostname='$target'";
799 $query_res = $bus_known_clients_db->select_dbentry( $sql_statement );
800 if( 1 == keys(%$query_res) ) {
801 my $host_name = $query_res->{1}->{'hostname'};
802 my $server_name = $query_res->{1}->{'registered'};
803 # fetch correct key for server
804 my $sql_statement = "SELECT * FROM bus_known_server WHERE hostname='$server_name'";
805 my $query_res = $bus_known_server_db->select_dbentry( $sql_statement );
806 my $server_key = $query_res->{1}->{'hostkey'};
807 &send_msg_to_target($msg, $server_name, $server_key, $header);
808 next;
809 }
811 daemon_log("ERROR:unknown host, can not send message '$header' to target '$target'", 1);
812 }
813 elsif( $target =~ /([0-9a-f]{2}:[0-9a-f]{2}:[0-9a-f]{2}:[0-9a-f]{2}:[0-9a-f]{2}:[0-9a-f]{2})/ ) {
814 # target is a mac address
815 my $sql_statement = "SELECT * FROM bus_known_clients WHERE macaddress LIKE '$target'";
816 my $query_res = $bus_known_clients_db->select_dbentry( $sql_statement );
817 if( 1 > keys(%{$query_res})) {
818 daemon_log("ERROR: there are more than one hosts in bus_known_clients_db with mac address '$target'", 1);
819 }
820 elsif( 0 == keys(%{$query_res})) {
821 daemon_log("WARNING: no host found in bus_known_clients_db with mac address '$target'", 3);
822 }
823 else {
824 my $host_name = $query_res->{1}->{'hostname'};
825 my $server_name = $query_res->{1}->{'registered'};
826 my $out_msg = $msg;
827 $out_msg =~ s/<target>$target<\/target>/<target>$host_name<\/target>/;
829 # fetch correct key for server
830 my $sql_statement = "SELECT * FROM bus_known_server WHERE hostname='$server_name'";
831 my $query_res = $bus_known_server_db->select_dbentry( $sql_statement );
832 my $server_key = $query_res->{1}->{'hostkey'};
834 &send_msg_to_target($out_msg, $server_name, $server_key, $header);
835 }
836 }
837 else {
838 daemon_log("ERROR: target address '$target' does not match neiter ".
839 "to ip address nor to mac address, can not send msg", 1);
840 }
842 }
843 }
844 }
845 }
848 sub here_i_am {
849 my ( $msg, $msg_hash ) = @_[ ARG0, ARG1 ];
850 my $source = @{$msg_hash->{'source'}}[0];
851 my $target = @{$msg_hash->{'target'}}[0];
853 my $new_key = &create_passwd();
855 # create bus_known_server entry
856 my $add_hash = {
857 table=>"bus_known_server",
858 primkey=>"hostname",
859 hostname=>$source,
860 status=>"registered",
861 hostkey=>$bus_key,
862 };
863 $bus_known_server_db->add_dbentry($add_hash);
865 # create outgoing msg
866 my $out_hash = &create_xml_hash("new_key", $target, $source, $new_key);
867 &send_msg_hash2address($out_hash, $source, $bus_key);
869 # change hostkey, reason
870 my $where_str= " WHERE hostname='$source'";
871 my $update_str= " SET hostkey='$new_key'";
872 my $sql_statement= "UPDATE bus_known_server $update_str $where_str";
873 $bus_known_server_db->update_dbentry($sql_statement);
874 }
877 sub confirm_new_key {
878 my ( $msg, $msg_hash ) = @_[ ARG0, ARG1 ];
879 my $source = @{$msg_hash->{'source'}}[0];
880 daemon_log("'$source' confirms new key", 3);
881 }
884 sub new_client {
885 my ($msg, $msg_hash) = @_[ ARG0, ARG1 ];
887 my $new_client = @{$msg_hash->{'new_client'}}[0];
888 my $source = @{$msg_hash->{'source'}}[0];
889 my $mac_address = @{$msg_hash->{'macaddress'}}[0];
890 my $act_timestamp = @{$msg_hash->{'timestamp'}}[0];
892 my $add_hash = {
893 table => "bus_known_clients",
894 primkey=>"hostname",
895 hostname=>$new_client,
896 status=>'activ',
897 registered=>$source,
898 macaddress=>$mac_address,
899 timestamp=>$act_timestamp,
900 };
901 $bus_known_clients_db->add_dbentry($add_hash);
902 daemon_log("add new client '$new_client' to bus_known_clients_db", 3);
903 }
907 #==== MAIN = main ==============================================================
910 # parse commandline options
911 Getopt::Long::Configure( "bundling" );
912 GetOptions("h|help" => \&usage,
913 "c|config=s" => \$cfg_file,
914 "f|foreground" => \$foreground,
915 "v|verbose+" => \$verbose,
916 );
918 # read and set config parameters
919 &check_cmdline_param ;
920 &read_configfile;
921 &check_pid;
923 $SIG{CHLD} = 'IGNORE';
926 # forward error messages to logfile
927 if ( ! $foreground ) {
928 open( STDIN, '+>/dev/null' );
929 open( STDOUT, '+>&STDIN' );
930 open( STDERR, '+>&STDIN' );
931 }
933 # Just fork, if we are not in foreground mode
934 if( ! $foreground ) {
935 chdir '/' or die "Can't chdir to /: $!";
936 $pid = fork;
937 setsid or die "Can't start a new session: $!";
938 umask 0;
939 }
940 else {
941 $pid = $$;
942 }
944 # Do something useful - put our PID into the pid_file
945 if( 0 != $pid ) {
946 open( LOCK_FILE, ">$pid_file" );
947 print LOCK_FILE "$pid\n";
948 close( LOCK_FILE );
949 if( !$foreground ) {
950 exit( 0 )
951 };
952 }
954 # restart daemon log file
955 if(-e $log_file ) { unlink $log_file }
956 daemon_log(" ", 1);
957 daemon_log("started!", 1);
959 # delete old DBsqlite lock files
960 system('rm -f /tmp/gosa_si_lock*gosa-si-bus*');
962 #prepare other variables
963 $xml = new XML::Simple();
964 $bus_address = "$bus_ip:$bus_port";
966 # detect ip and mac address and complete host address
967 my $network_interface= &get_interface_for_ip($bus_ip);
968 $bus_mac_address= &get_mac($network_interface);
969 daemon_log("gosa-si-bus ip address detected: $bus_ip", 1);
970 daemon_log("gosa-si-bus mac address detected: $bus_mac_address", 1);
972 # connect to bus_known_server_db
973 my @server_col_names = ('hostname', 'status', 'hostkey', 'timestamp');
974 $bus_known_server_db = GOSA::DBsqlite->new($bus_known_server_file_name);
975 $bus_known_server_db->create_table('bus_known_server', \@server_col_names);
977 my @clients_col_names = ('hostname', 'status', 'registered', 'macaddress', 'timestamp');
978 $bus_known_clients_db = GOSA::DBsqlite->new($bus_known_clients_file_name);
979 $bus_known_clients_db->create_table('bus_known_clients', \@clients_col_names);
981 # create socket for incoming xml messages
982 POE::Component::Server::TCP->new(
983 Alias => 'gosa-si-bus_socket',
984 Port => $bus_port,
985 ClientInput => \&bus_input,
986 );
987 daemon_log("start socket for incoming xml messages at port '$bus_port' ", 1);
989 # start session
990 POE::Session->create(
991 inline_states => {
992 _start => \&_start,
993 _default => \&_default,
994 here_i_am => \&here_i_am,
995 confirm_new_key => \&confirm_new_key,
996 new_client => \&new_client,
997 }
998 );
1000 POE::Kernel->run();
1001 exit;