1 #!/usr/bin/perl
2 #===============================================================================
3 #
4 # FILE: gosa-server
5 #
6 # USAGE: ./gosa-server
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 IO::Socket::INET;
29 use Crypt::Rijndael;
30 use MIME::Base64;
31 use Digest::MD5 qw(md5 md5_hex md5_base64);
32 use XML::Simple;
33 use Data::Dumper;
34 use Sys::Syslog qw( :DEFAULT setlogsock);
35 use Cwd;
36 use File::Spec;
37 use GOSA::GosaSupportDaemon;
38 use GOSA::DBsqlite;
40 my ($cfg_file, $default_cfg_file, %cfg_defaults, $foreground, $verbose);
41 my ($bus_activ, $bus_passwd, $bus_ip, $bus_port, $bus_address, $bus, $bus_mac_address);
42 my ($pid_file, $procid, $pid, $log_file, $my_own_address);
43 my (%free_child, %busy_child, $child_max, $child_min, %child_alive_time, $child_timeout);
44 my ($bus_known_server_db, $bus_known_server_file_name);
45 my ($xml, $bus_cipher);
47 $foreground = 0 ;
49 %cfg_defaults =
50 ("general" =>
51 {"log_file" => [\$log_file, "/var/run/".$0.".log"],
52 "pid_file" => [\$pid_file, "/var/run/".$0.".pid"],
53 "child_max" => [\$child_max, 10],
54 "child_min" => [\$child_min, 3],
55 "child_timeout" => [\$child_timeout, 180],
56 "bus_known_server_file_name" => [\$bus_known_server_file_name, "/var/lib/gosa-si/bus_known_server.db"]
57 },
58 "bus" =>
59 {"bus_activ" => [\$bus_activ, "on"],
60 "bus_passwd" => [\$bus_passwd, ""],
61 "bus_port" => [\$bus_port, "20080"],
62 }
63 );
65 #=== FUNCTION ================================================================
66 # NAME: read_configfile
67 # PARAMETERS: cfg_file - string -
68 # RETURNS: nothing
69 # DESCRIPTION: read cfg_file and set variables
70 #===============================================================================
71 sub read_configfile {
72 my $cfg;
73 if( defined( $cfg_file) && ( length($cfg_file) > 0 )) {
74 if( -r $cfg_file ) {
75 $cfg = Config::IniFiles->new( -file => $cfg_file );
76 } else {
77 print STDERR "Couldn't read config file!";
78 }
79 } else {
80 $cfg = Config::IniFiles->new() ;
81 }
82 foreach my $section (keys %cfg_defaults) {
83 foreach my $param (keys %{$cfg_defaults{ $section }}) {
84 my $pinfo = $cfg_defaults{ $section }{ $param };
85 ${@$pinfo[ 0 ]} = $cfg->val( $section, $param, @$pinfo[ 1 ] );
86 }
87 }
88 }
90 #=== FUNCTION ================================================================
91 # NAME: logging
92 # PARAMETERS: level - string - default 'info'
93 # msg - string -
94 # facility - string - default 'LOG_DAEMON'
95 # RETURNS: nothing
96 # DESCRIPTION: function for logging
97 #===============================================================================
98 sub daemon_log {
99 my( $msg, $level ) = @_;
100 if(not defined $msg) { return }
101 if(not defined $level) { $level = 1 }
102 if(defined $log_file){
103 open(LOG_HANDLE, ">>$log_file");
104 if(not defined open( LOG_HANDLE, ">>$log_file" )) {
105 print STDERR "cannot open $log_file: $!";
106 return }
107 chomp($msg);
108 if($level <= $verbose){
109 print LOG_HANDLE $msg."\n";
110 if(defined $foreground) { print $msg."\n" }
111 }
112 }
113 close( LOG_HANDLE );
114 # my ($msg, $level, $facility) = @_;
115 # if(not defined $msg) {return}
116 # if(not defined $level) {$level = "info"}
117 # if(not defined $facility) {$facility = "LOG_DAEMON"}
118 # openlog($0, "pid,cons,", $facility);
119 # syslog($level, $msg);
120 # closelog;
121 # return;
122 }
124 #=== FUNCTION ================================================================
125 # NAME: check_cmdline_param
126 # PARAMETERS: nothing
127 # RETURNS: nothing
128 # DESCRIPTION: validates commandline parameter
129 #===============================================================================
130 sub check_cmdline_param () {
131 my $err_config;
132 my $err_counter = 0;
133 if( not defined( $cfg_file)) {
134 my $cwd = getcwd;
135 my $name = "/etc/gosa-si/bus.conf";
136 $cfg_file = File::Spec->catfile( $cwd, $name );
137 }
138 if( $err_counter > 0 ) {
139 &usage( "", 1 );
140 if( defined( $err_config)) { print STDERR "$err_config\n"}
141 print STDERR "\n";
142 exit( -1 );
143 }
144 }
146 #=== FUNCTION ================================================================
147 # NAME: check_pid
148 # PARAMETERS: nothing
149 # RETURNS: nothing
150 # DESCRIPTION: handels pid processing
151 #===============================================================================
152 sub check_pid {
153 $pid = -1;
154 # Check, if we are already running
155 if( open(LOCK_FILE, "<$pid_file") ) {
156 $pid = <LOCK_FILE>;
157 if( defined $pid ) {
158 chomp( $pid );
159 if( -f "/proc/$pid/stat" ) {
160 my($stat) = `cat /proc/$pid/stat` =~ m/$pid \((.+)\).*/;
161 if( $0 eq $stat ) {
162 close( LOCK_FILE );
163 exit -1;
164 }
165 }
166 }
167 close( LOCK_FILE );
168 unlink( $pid_file );
169 }
171 # create a syslog msg if it is not to possible to open PID file
172 if (not sysopen(LOCK_FILE, $pid_file, O_WRONLY|O_CREAT|O_EXCL, 0644)) {
173 my($msg) = "Couldn't obtain lockfile '$pid_file' ";
174 if (open(LOCK_FILE, '<', $pid_file)
175 && ($pid = <LOCK_FILE>))
176 {
177 chomp($pid);
178 $msg .= "(PID $pid)\n";
179 } else {
180 $msg .= "(unable to read PID)\n";
181 }
182 if( ! ($foreground) ) {
183 openlog( $0, "cons,pid", "daemon" );
184 syslog( "warning", $msg );
185 closelog();
186 }
187 else {
188 print( STDERR " $msg " );
189 }
190 exit( -1 );
191 }
192 }
195 #=== FUNCTION ================================================================
196 # NAME: usage
197 # PARAMETERS: nothing
198 # RETURNS: nothing
199 # DESCRIPTION: print out usage text to STDERR
200 #===============================================================================
201 sub usage {
202 print STDERR << "EOF" ;
203 usage: $0 [-hvf] [-c config]
205 -h : this (help) message
206 -c <file> : config file
207 -f : foreground, process will not be forked to background
208 -v : be verbose (multiple to increase verbosity)
209 EOF
210 print "\n" ;
211 }
214 #=== FUNCTION ================================================================
215 # NAME: sig_int_handler
216 # PARAMETERS: signal - string - signal arose from system
217 # RETURNS: noting
218 # DESCRIPTION: handels tasks to be done befor signal becomes active
219 #===============================================================================
220 sub sig_int_handler {
221 my ($signal) = @_;
222 if($bus){
223 close($bus);
224 print "$bus closed\n";
225 }
226 print "$signal\n";
227 exit(1);
228 }
229 $SIG{INT} = \&sig_int_handler;
232 #=== FUNCTION ================================================================
233 # NAME: get_ip_and_mac
234 # PARAMETERS: nothing
235 # RETURNS: (ip, mac)
236 # DESCRIPTION: executes /sbin/ifconfig and parses the output, the first occurence
237 # of a inet address is returned as well as the mac address in the line
238 # above the inet address
239 #===============================================================================
240 sub get_ip_and_mac {
241 my $ip = "0.0.0.0.0"; # Defualt-IP
242 my $mac_address = "00:00:00:00:00:00"; # Default-MAC
243 my @ifconfig = qx(/sbin/ifconfig);
244 foreach(@ifconfig) {
245 if (/Hardware Adresse (\S{2}):(\S{2}):(\S{2}):(\S{2}):(\S{2}):(\S{2})/) {
246 $mac_address = "$1:$2:$3:$4:$5:$6";
247 next;
248 }
249 if (/inet Adresse:(\d+).(\d+).(\d+).(\d+)/) {
250 $ip = "$1.$2.$3.$4";
251 last;
252 }
253 }
254 return ($ip, $mac_address);
255 }
259 #=== FUNCTION ================================================================
260 # NAME: activating_child
261 # PARAMETERS: msg - string - incoming message
262 # host - string - host from which the incomming message comes
263 # RETURNS: nothing
264 # DESCRIPTION: handels the distribution of incoming messages to working childs
265 #===============================================================================
266 sub activating_child {
267 my ($msg, $host) = @_;
268 my $child = &get_processing_child();
269 my $pipe_wr = $$child{'pipe_wr'};
270 daemon_log("activating: childpid: $$child{'pid'}", 5);
271 print $pipe_wr $msg.".".$host."\n";
272 return;
273 }
276 #=== FUNCTION ================================================================
277 # NAME: get_processing_child
278 # PARAMETERS: nothing
279 # RETURNS: child - hash - holding the process id and the references to the pipe
280 # handles pipe_wr and pipe_rd
281 # DESCRIPTION: handels the forking, reactivating and keeping alive tasks
282 #===============================================================================
283 sub get_processing_child {
284 my $child;
285 # checking %busy_child{pipe_wr} if msg is 'done', then set child from busy to free
286 while(my ($key, $val) = each(%busy_child)) {
287 # check wether process still exists
288 my $exitus_pid = waitpid($key, WNOHANG);
289 if($exitus_pid != 0) {
290 delete $busy_child{$key};
291 daemon_log( "prozess:$key wurde aus busy_child entfernt\n", 5);
292 next;
293 }
295 # check wether process sitll works
296 my $fh = $$val{'pipe_rd'};
297 $fh->blocking(0);
298 my $child_answer;
299 if(not $child_answer = <$fh>) { next }
300 chomp($child_answer);
301 if($child_answer eq "done") {
302 delete $busy_child{$key};
303 $free_child{$key} = $val;
304 }
305 }
307 while(my ($key, $val) = each(%free_child)) {
308 my $exitus_pid = waitpid($key, WNOHANG);
309 if($exitus_pid != 0) {
310 delete $free_child{$key};
311 daemon_log( "prozess:$key wurde aus free_child entfernt\n", 5);
312 }
313 daemon_log("free child:$key\n", 5);
314 }
315 # check @free_child and @busy_child
316 my $free_len = scalar(keys(%free_child));
317 my $busy_len = scalar(keys(%busy_child));
318 daemon_log("free children $free_len, busy children $busy_len\n",5);
320 # if there is a free child, let the child work
321 if($free_len > 0){
322 my @keys = keys(%free_child);
323 $child = $free_child{$keys[0]};
324 if(defined $child) {
325 $busy_child{$$child{'pid'}} = $child ;
326 delete $free_child{$$child{'pid'}};
327 }
328 return $child;
329 }
331 # no free child, try to fork another one
332 if($free_len + $busy_len < $child_max) {
334 daemon_log("not enough children, create a new one\n",5);
336 # New pipes for communication
337 my( $PARENT_wr, $PARENT_rd );
338 my( $CHILD_wr, $CHILD_rd );
339 pipe( $CHILD_rd, $PARENT_wr );
340 pipe( $PARENT_rd, $CHILD_wr );
341 $PARENT_wr->autoflush(1);
342 $CHILD_wr->autoflush(1);
344 ############
345 # fork child
346 ############
347 my $child_pid = fork();
349 #CHILD
350 if($child_pid == 0) {
351 # Close unused pipes
352 close( $CHILD_rd );
353 close( $CHILD_wr );
354 while( 1 ) {
355 my $rbits = "";
356 vec( $rbits, fileno $PARENT_rd , 1 ) = 1;
358 # waiting child_timeout for jobs to do
359 my $nf = select($rbits, undef, undef, $child_timeout);
360 if($nf < 0 ) {
361 # if $nf < 1, error handling
362 die "select(): $!\n";
363 } elsif (! $nf) {
364 # if already child_min childs are alive, then leave loop
365 $free_len = scalar(keys(%free_child));
366 $busy_len = scalar(keys(%busy_child));
367 if($free_len + $busy_len >= $child_min) {
368 last;
369 } else {
370 redo;
371 }
372 }
374 # a job for a child arise
375 if ( vec $rbits, fileno $PARENT_rd, 1 ) {
376 # read everything from pipe
377 my $msg = "";
378 $PARENT_rd->blocking(0);
379 while(1) {
380 my $read = <$PARENT_rd>;
381 if(not defined $read) { last}
382 $msg .= $read;
383 }
385 # forward the job msg to another function
386 &process_incoming_msg($msg);
387 daemon_log("processing of msg finished", 5);
389 # important!!! wait until child says 'done', until then child is set from busy to free
390 print $PARENT_wr "done";
391 redo;
392 }
393 }
394 # childs leaving the loop are allowed to die
395 exit(0);
397 #PARENT
398 } else {
399 # Close unused pipes
400 close( $PARENT_rd );
401 close( $PARENT_wr );
402 # add child to child alive hash
403 my %child_hash = (
404 'pid' => $child_pid,
405 'pipe_wr' => $CHILD_wr,
406 'pipe_rd' => $CHILD_rd,
407 );
409 $child = \%child_hash;
410 $busy_child{$$child{'pid'}} = $child;
411 return $child;
412 }
413 }
414 }
417 #=== FUNCTION ================================================================
418 # NAME: process_incoming_msg
419 # PARAMETERS: crypted_msg - string - incoming crypted message
420 # RETURNS: nothing
421 # DESCRIPTION: handels the proceeded distribution to the appropriated functions
422 #===============================================================================
423 sub process_incoming_msg {
424 my ($crypted_msg) = @_;
425 if(not defined $crypted_msg) {
426 daemon_log("function 'process_incoming_msg': got no msg", 7);
427 return;
428 }
429 $crypted_msg =~ /^([\s\S]*?)\.(\d{1,3}?)\.(\d{1,3}?)\.(\d{1,3}?)\.(\d{1,3}?)$/;
430 $crypted_msg = $1;
431 my $host = sprintf("%s.%s.%s.%s", $2, $3, $4, $5);
433 my $msg;
434 my $msg_hash;
435 my $host_name;
436 my $host_key;
438 # check wether incoming msg is a new msg
439 $host_name = $bus_address;
440 $host_key = $bus_passwd;
441 daemon_log("process_incoming_msg: host_name: $host_name", 7);
442 daemon_log("process_incoming_msg: host_key: $host_key", 7);
443 eval{
444 my $key_cipher = &create_ciphering($host_key);
445 $msg = &decrypt_msg($crypted_msg, $key_cipher);
446 $msg_hash = &transform_msg2hash($msg);
447 };
448 if($@) {
449 daemon_log("process_incoming_msg: deciphering raise error", 7);
450 daemon_log("$@", 8);
451 $msg = undef;
452 $msg_hash = undef;
453 $host_name = undef;
454 $host_key = undef;
455 }
457 # check wether incoming msg is from a bus_known_server
458 if( not defined $msg ) {
459 my $query_res = $bus_known_server_db->select_dbentry( {table=>'bus_known_server'} );
460 while( my ($hit_num, $hit) = each %{ $query_res } ) {
461 $host_name = $hit->{hostname};
462 if( not $host_name =~ "^$host") {
463 next;
464 }
465 $host_key = $hit->{hostkey};
466 daemon_log("process_incoming_msg: host_name: $host_name", 7);
467 daemon_log("process_incoming_msg: host_key: $host_key", 7);
468 eval{
469 my $key_cipher = &create_ciphering($host_key);
470 $msg = &decrypt_msg($crypted_msg, $key_cipher);
471 $msg_hash = &transform_msg2hash($msg);
472 };
473 if($@) {
474 daemon_log("process_incoming_msg: deciphering raise error", 7);
475 daemon_log("$@", 8);
476 $msg = undef;
477 $msg_hash = undef;
478 $host_name = undef;
479 $host_key = undef;
480 } else {
481 last;
482 }
483 }
484 }
486 if( not defined $msg ) {
487 daemon_log("WARNING: bus does not understand the message:", 5);
488 return;
489 }
491 # process incoming msg
492 my $header = @{$msg_hash->{header}}[0];
493 my $source = @{$msg_hash->{source}}[0];
495 daemon_log("header from msg: $header", 1);
496 daemon_log("msg to process:", 5);
497 daemon_log($msg, 5);
499 my @targets = @{$msg_hash->{target}};
500 my $len_targets = @targets;
502 if ($len_targets == 0){
503 daemon_log("ERROR: no target specified for msg $header", 1);
505 } elsif ($len_targets == 1){
506 # we have only one target symbol
507 my $target = $targets[0];
508 daemon_log("msg is for: $target", 7);
510 if($target eq $bus_address) {
511 # msg is for bus
512 if($header eq 'here_i_am'){ &here_i_am($msg_hash)}
513 elsif($header eq 'confirm_new_passwd'){ &confirm_new_passwd($msg_hash)}
514 elsif($header eq 'got_ping') { &got_ping($msg_hash)}
515 elsif($header eq 'ping') { &ping($msg_hash)}
516 elsif($header eq 'who_has') { &who_has($msg_hash)}
517 elsif($header eq 'new_client') { &new_client($msg_hash)}
518 elsif($header eq 'delete_client') { &delete_client($msg_hash)}
520 } elsif ($target eq "*"){
521 # msg is for all server
522 my $query_res = $bus_known_server_db->select_dbentry( {table=>'known_server'} );
523 while( my ($hit_num, $hit) = each %{ $query_res } ) {
524 $host_name = $hit->{hostname};
525 $host_key = $hit->{hostkey};
526 $msg_hash->{target} = [$host_name];
527 &send_msg_hash2address($msg_hash, $host_name, $host_key);
528 }
529 return;
530 }
532 } else {
533 # a list of targets is specified
534 my $target_address;
535 foreach $target_address (@targets) {
537 my $query_res = $bus_known_server_db->select_dbentry( {table=>'known_server', hostname=>$target_address} );
538 if( 1 == keys %{$query_res} ) {
539 $host_key = $query_res->{1}->{hostkey};
540 &send_msg_hash2address($msg_hash, $target_address, $host_key);
541 next;
543 } else {
544 $query_res = $bus_known_server_db->select_dbentry( {table=>'known_server'} );
545 while( my ($hit_num, $hit) = each %{$query_res} ) {
546 my $host_name = $hit->{hostname};
547 my $host_key = $hit->{hostkey};
548 my $clients = $hit->{clients};
549 my @clients = split(/,/, $clients);
550 foreach my $client (@clients) {
551 if( not $client eq $target_address ) {
552 next;
553 }
554 $msg_hash->{target} = [ $target_address ];
555 &send_msg_hash2address($msg_hash, $host_name, $host_key);
556 daemon_log("bus forwards msg $header for client $target_address to server $host_name", 3);
557 last;
558 }
559 }
560 }
561 }
562 }
564 return;
565 }
568 #=== FUNCTION ================================================================
569 # NAME: get_content_of_known_daemons
570 # PARAMETERS:
571 # RETURNS:
572 # DESCRIPTION:
573 #===============================================================================
574 #sub get_content_of_known_daemons {
575 # my ($host, $content) = @_;
576 # return;
577 #}
580 #=== FUNCTION ================================================================
581 # NAME: create_passwd
582 # PARAMETERS: nothing
583 # RETURNS: new_passwd - string
584 # DESCRIPTION: creates a 32 bit long random passwd out of "a".."z","A".."Z",0..9
585 #===============================================================================
586 sub create_passwd {
587 my $new_passwd = "";
588 for(my $i=0; $i<31; $i++) {
589 $new_passwd .= ("a".."z","A".."Z",0..9)[int(rand(62))]
590 }
591 return $new_passwd;
592 }
595 #=== FUNCTION ================================================================
596 # NAME: create_ciphering
597 # PARAMETERS: passwd - string - used to create ciphering
598 # RETURNS: cipher - object
599 # DESCRIPTION: creates a Crypt::Rijndael::MODE_CBC object with passwd as key
600 #===============================================================================
601 #sub create_ciphering {
602 # my ($passwd) = @_;
603 # $passwd = substr(md5_hex("$passwd") x 32, 0, 32);
604 # my $iv = substr(md5_hex('GONICUS GmbH'),0, 16);
605 #
606 # my $my_cipher = Crypt::Rijndael->new($passwd , Crypt::Rijndael::MODE_CBC());
607 # $my_cipher->set_iv($iv);
608 # return $my_cipher;
609 #}
612 #=== FUNCTION ================================================================
613 # NAME: encrypt_msg
614 # PARAMETERS: msg - string - message to encrypt
615 # my_cipher - ref - reference to a Crypt::Rijndael object
616 # RETURNS: crypted_msg - string - crypted message
617 # DESCRIPTION: crypts the incoming message with the Crypt::Rijndael module
618 #===============================================================================
619 #sub encrypt_msg {
620 # my ($msg, $my_cipher) = @_;
621 # if(not defined $my_cipher) { print "no cipher object\n"; }
622 # $msg = "\0"x(16-length($msg)%16).$msg;
623 # my $crypted_msg = $my_cipher->encrypt($msg);
624 # chomp($crypted_msg = &encode_base64($crypted_msg));
625 # return $crypted_msg;
626 #}
629 #=== FUNCTION ================================================================
630 # NAME: decrypt_msg
631 # PARAMETERS: crypted_msg - string - message to decrypt
632 # my_cipher - ref - reference to a Crypt::Rijndael object
633 # RETURNS: msg - string - decrypted message
634 # DESCRIPTION: decrypts the incoming message with the Crypt::Rijndael module
635 #===============================================================================
636 #sub decrypt_msg {
637 # my ($crypted_msg, $my_cipher) = @_ ;
638 # $crypted_msg = &decode_base64($crypted_msg);
639 # my $msg = $my_cipher->decrypt($crypted_msg);
640 # $msg =~ s/^\0*//g;
641 # return $msg;
642 #}
645 #=== FUNCTION ================================================================
646 # NAME: create_xml_hash
647 # PARAMETERS: header - string - message header (required)
648 # source - string - where the message come from (required)
649 # target - string - where the message should go to (required)
650 # [header_value] - string - something usefull (optional)
651 # RETURNS: hash - hash - nomen est omen
652 # DESCRIPTION: creates a key-value hash, all values are stored in a array
653 #===============================================================================
654 #sub create_xml_hash {
655 # my ($header, $source, $target, $header_value) = @_ ;
656 #
657 # if (not defined $header || not defined $source || not defined $target) {
658 # daemon_log("ERROR: create_xml_hash function is invoked with uncompleted parameters", 7);
659 # }
660 #
661 # my $hash = {
662 # header => [$header],
663 # source => [$source],
664 # target => [$target],
665 # $header => [$header_value],
666 # };
667 # #daemon_log("create_xml_hash:", 7),
668 # #chomp(my $tmp = Dumper $hash);
669 # #daemon_log("\t$tmp\n", 7);
670 # return $hash
671 #}
674 #=== FUNCTION ================================================================
675 # NAME: create_xml_string
676 # PARAMETERS: xml_hash - hash - hash from function create_xml_hash
677 # RETURNS: xml_string - string - xml string representation of the hash
678 # DESCRIPTION: transform the hash to a string using XML::Simple module
679 #===============================================================================
680 #sub create_xml_string {
681 # my ($xml_hash) = @_ ;
682 # my $xml_string = $xml->XMLout($xml_hash, RootName => 'xml');
683 # #$xml_string =~ s/[\n]+//g;
684 # return $xml_string;
685 #}
688 #=== FUNCTION ================================================================
689 # NAME: add_content2xml_hash
690 # PARAMETERS: xml_ref - ref - reference to a hash from function create_xml_hash
691 # element - string - key for the hash
692 # content - string - value for the hash
693 # RETURNS: nothing
694 # DESCRIPTION: add key-value pair to xml_ref, if key alread exists, then append value to list
695 #===============================================================================
696 #sub add_content2xml_hash {
697 # my ($xml_ref, $element, $content) = @_;
698 # if(not exists $$xml_ref{$element} ) {
699 # $$xml_ref{$element} = [];
700 # }
701 # my $tmp = $$xml_ref{$element};
702 # push(@$tmp, $content);
703 # return;
704 #}
707 #=== FUNCTION ================================================================
708 # NAME: get_content_from_xml_hash
709 # PARAMETERS: xml_ref - ref - reference of the xml hash
710 # element - string - key of the value you want
711 # RETURNS: value - string - if key is either header, target or source
712 # value - list - for all other keys in xml hash
713 # DESCRIPTION:
714 #===============================================================================
715 #sub get_content_from_xml_hash {
716 # my ($xml_ref, $element) = @_;
717 # my $result = $xml_ref->{$element};
718 # if( $element eq "header" || $element eq "target" || $element eq "source") {
719 # return @$result[0];
720 # }
721 # return @$result;
722 #}
725 #=== FUNCTION ================================================================
726 # NAME: open_socket
727 # PARAMETERS: PeerAddr - string - something like 192.168.1.1 or 192.168.1.1:10000
728 # [PeerPort] - string - necessary if port not appended by PeerAddr
729 # RETURNS: socket - IO::Socket::INET
730 # DESCRIPTION: open a socket to PeerAddr
731 #===============================================================================
732 #sub open_socket {
733 # my ($PeerAddr, $PeerPort) = @_ ;
734 # if(defined($PeerPort)){
735 # $PeerAddr = $PeerAddr.":".$PeerPort;
736 # }
737 # my $socket;
738 # $socket = new IO::Socket::INET(PeerAddr => $PeerAddr ,
739 # Porto => "tcp" ,
740 # Type => SOCK_STREAM,
741 # Reuse => 1,
742 # Timeout => 5,
743 # );
744 # if(not defined $socket) {
745 # return;
746 # }
747 # return $socket;
748 #}
751 #=== FUNCTION ================================================================
752 # NAME: read_from_socket
753 # PARAMETERS: socket - fh - filehandel to read from
754 # RETURNS: result - string - readed characters from socket
755 # DESCRIPTION: reads data from socket in 16 byte steps
756 #===============================================================================
757 sub read_from_socket {
758 my ($socket) = @_;
760 $socket->blocking(1);
761 my $result = <$socket>;
762 $socket->blocking(0);
763 my $part_msg;
764 while ($part_msg = <$socket>) {
765 if (not defined $part_msg) { last; }
766 $result .= $part_msg;
767 }
769 #my $result = "";
770 #my $len = 16;
771 #while($len == 16){
772 # my $char;
773 # $len = sysread($socket, $char, 16);
774 # if($len != 16) { last }
775 # if($len != 16) { last }
776 # $result .= $char;
777 #}
778 return $result;
779 }
782 #=== FUNCTION ================================================================
783 # NAME: send_msg_hash2address
784 # PARAMETERS: msg_hash - hash - xml_hash created with function create_xml_hash
785 # PeerAddr string - socket address to send msg
786 # PeerPort string - socket port, if not included in socket address
787 # RETURNS: nothing
788 # DESCRIPTION: ????
789 #===============================================================================
790 #sub send_msg_hash2address {
791 # my ($msg_hash, $address) = @_ ;
792 #
793 # # fetch header for logging
794 # my $header = &get_content_from_xml_hash($msg_hash, "header");
795 #
796 # # generate xml string
797 # my $msg_xml = &create_xml_string($msg_hash);
798 #
799 # # fetch the appropriated passwd from hash
800 # my $passwd = $known_daemons->{$address}->{passwd};
801 #
802 # # create a ciphering object
803 # my $act_cipher = &create_ciphering($passwd);
804 #
805 # # encrypt xml msg
806 # my $crypted_msg = &encrypt_msg($msg_xml, $act_cipher);
807 #
808 # # open socket
809 # my $socket = &open_socket($address);
810 # if(not defined $socket){
811 # daemon_log("ERROR: cannot send '$header'-msg to $address , server not reachable", 1);
812 # return;
813 # }
814 #
815 # # send xml msg
816 # print $socket $crypted_msg."\n";
817 #
818 # close $socket;
819 # daemon_log("send '$header'-msg to $address", 5);
820 # daemon_log("crypted_msg:\n\t$crypted_msg", 7);
821 # return;
822 #}
825 #=== FUNCTION ================================================================
826 # NAME: send_msg_hash2all
827 # PARAMETERS: msg_hash - hash - xml_hash created with function create_xml_hash
828 # RETURNS: nothing
829 # DESCRIPTION: send msg_hash to all registered daemons
830 #===============================================================================
831 #sub send_msg_hash2all {
832 # my ($msg_hash) = @_;
833 #
834 # # fetch header for logging
835 # my $header = &get_content_from_xml_hash($msg_hash, "header");
836 #
837 # # generate xml string
838 # my $msg_xml = &create_xml_string($msg_hash);
839 #
840 # # fetch a list of all target addresses
841 # my @targets = keys(%$known_daemons);
842 #
843 # # itterates through the list an send each the msg
844 # foreach my $target (@targets) {
845 # if($target eq $bus_address) {next}; # do not send msg to bus
846 #
847 # # fetch the appropriated passwd
848 # my $passwd = $known_daemons->{$target}->{passwd};
849 #
850 # # create ciphering object
851 # my $act_cipher = &create_ciphering($passwd);
852 #
853 # # encrypt xml msg
854 # my $crypted_msg = &encrypt_msg($msg_xml, $act_cipher);
855 #
856 # # open socket
857 # my $socket = &open_socket($target);
858 # if(not defined $socket){
859 # daemon_log("ERROR: cannot open socket to $target , server not reachable", 1);
860 # &update_known_daemons_entry(hostname=>$target, status=>"down");
861 # next;
862 # }
863 #
864 # # send xml msg
865 # print $socket $crypted_msg."\n";
866 #
867 # close $socket;
868 # daemon_log("send '$header'-msg to $target", 5);
869 # daemon_log("crypted_msg:\n\t$crypted_msg", 7);
870 # }
871 # return;
872 #}
875 #=== FUNCTION ================================================================
876 # NAME: here_i_am
877 # PARAMETERS: msg_hash - hash - hash from function create_xml_hash
878 # RETURNS: nothing
879 # DESCRIPTION: process the incoming msg 'here_i_am'
880 #===============================================================================
881 sub here_i_am {
882 my ($msg_hash) = @_ ;
883 my $source = @{$msg_hash->{source}}[0];;
885 my $new_key = &create_passwd();
887 # create bus_known_server entry
888 my $add_hash = {
889 table=>"bus_known_server",
890 primkey=>"hostname",
891 hostname=>$source,
892 status=>"registered",
893 hostkey=>$bus_passwd,
894 clients=>"",
895 };
896 $bus_known_server_db->add_dbentry($add_hash);
898 # create outgoing msg
899 my $out_hash = &create_xml_hash("new_passwd", $bus_address, $source, $new_key);
900 &send_msg_hash2address($out_hash, $source, $bus_passwd);
902 # change hostkey, reason
903 my $update_hash = { table=>'bus_known_server' };
904 $update_hash->{where} = [ { hostname=>[$source] } ];
905 $update_hash->{update} = [ { hostkey=>[$new_key] } ];
906 $bus_known_server_db->update_dbentry($update_hash);
908 return;
909 }
912 #=== FUNCTION ================================================================
913 # NAME: confirm_new_passwd
914 # PARAMETERS: msg_hash - hash - hash from function create_xml_hash
915 # RETURNS: nothing
916 # DESCRIPTION: process this incoming message
917 #===============================================================================
918 sub confirm_new_passwd {
919 my ($msg_hash) = @_ ;
920 my $source = @{$msg_hash->{source}}[0];
922 my $update_hash = { table=>'bus_known_server' };
923 $update_hash->{where} = [ { hostname=>[$source] } ];
924 $update_hash->{update} = [ { status=>['key_confirmed'] } ];
925 $bus_known_server_db->update_dbentry($update_hash);
927 return;
928 }
931 #=== FUNCTION ================================================================
932 # NAME: ping
933 # PARAMETERS: msg_hash - hash - hash from function create_xml_hash
934 # RETURNS: nothing
935 # DESCRIPTION: process this incoming message
936 #===============================================================================
937 sub ping {
938 my ($msg_hash) = @_ ;
939 my $header = @{$msg_hash->{header}}[0];
940 my $source = @{$msg_hash->{source}}[0];
942 my $update_hash = { table=>'bus_known_server',
943 where=> [ { hostname=>[$source] } ],
944 update=> [ { status=>$header } ],
945 };
946 $bus_known_server_db->update_dbentry($update_hash);
948 my $out_hash = &create_xml_hash("got_ping", $bus_address, $source);
950 my $res = $bus_known_server_db->select_dbentry( { table=>'bus_known_server', hostname=>$source } );
951 my $hostkey = $res->{1}->{hostkey};
952 &send_msg_hash2address($out_hash, $source, $hostkey);
954 return;
955 }
958 #=== FUNCTION ================================================================
959 # NAME: make ping
960 # PARAMETERS: address - string - address which should be pinged
961 # RETURNS: nothing
962 # DESCRIPTION: send ping message to address
963 #===============================================================================
964 #sub make_ping {
965 # my ($address) = @_;
966 # daemon_log("ping:$address\n", 1);
967 # my $out_hash = &create_xml_hash("ping", "$bus_ip:$bus_port", $address);
968 # &send_msg_hash2address($out_hash, $address);
969 # return;
970 #}
973 #=== FUNCTION ================================================================
974 # NAME: got_ping
975 # PARAMETERS: msg_hash - hash - hash from function create_xml_hash
976 # RETURNS: nothing
977 # DESCRIPTION: process this incoming message
978 #===============================================================================
979 sub got_ping {
980 my ($msg_hash) = @_;
981 my $source = @{$msg_hash->{source}}[0];
983 my $update_hash = { table=>'bus_known_server',
984 where=> [ { hostname=>[$source] } ],
985 update=> [ { status=>'got_ping' } ],
986 };
987 $bus_known_server_db->update_dbentry($update_hash);
989 return;
990 }
993 #=== FUNCTION ================================================================
994 # NAME: new_client
995 # PARAMETERS: msg_hash - hash - hash from function create_xml_hash
996 # RETURNS: nothing
997 # DESCRIPTION: process this incoming message
998 #===============================================================================
999 sub new_client {
1000 my ($msg_hash) = @_ ;
1001 my $source = @{$msg_hash->{source}}[0];
1002 my $header = @{$msg_hash->{header}}[0];
1003 my $new_client = @{$msg_hash->{$header}}[0];
1005 my $res = $bus_known_server_db->select_dbentry( { table=>'bus_known_server', hostname=>$source } );
1006 my $clients = $res->{1}->{clients};
1008 # if host has alread more clients, than just append
1009 if( length($clients) != 0 ) {
1010 $clients .= ",$new_client";
1011 } else {
1012 $clients = $new_client;
1013 }
1015 my $update_hash = { table=>'bus_known_server',
1016 where=>[ {hostname=>[$source] } ],
1017 update=>[ {clients=>[$clients] } ],
1018 };
1020 $bus_known_server_db->update_dbentry( $update_hash );
1021 return;
1022 }
1025 #=== FUNCTION ================================================================
1026 # NAME: delete_client
1027 # PARAMETERS: msg_hash - hash - hash from function create_xml_hash
1028 # RETURNS: nothing
1029 # DESCRIPTION: process this incoming message
1030 #===============================================================================
1031 #sub delete_client {
1032 # my ($msg_hash) = @_ ;
1033 # my $source = &get_content_from_xml_hash($msg_hash, "source");
1034 # my $header = &get_content_from_xml_hash($msg_hash, "header");
1035 # my $del_client = (&get_content_from_xml_hash($msg_hash, $header))[0];
1036 #
1037 # if (not exists $known_daemons->{$source}->{$del_client}) {
1038 # daemon_log
1039 # }
1040 # delete $known_daemons->{$source}->{$del_client};
1041 #
1042 # return;
1043 #}
1046 #=== FUNCTION ================================================================
1047 # NAME: print_known_daemons_hash
1048 # PARAMETERS: nothing
1049 # RETURNS: nothing
1050 # DESCRIPTION: nome est omen
1051 #===============================================================================
1052 #sub print_known_daemons_hash {
1053 # my ($tmp) = @_;
1054 # print "####################################\n";
1055 # print "# status of known_daemons\n";
1056 # my $hosts;
1057 # my $host_hash;
1058 # $shmkh->shlock(LOCK_EX);
1059 # my @hosts = keys %$known_daemons;
1060 # foreach my $host (@hosts) {
1061 # my $status = $known_daemons->{$host}->{status} ;
1062 # my $passwd = $known_daemons->{$host}->{passwd};
1063 # my $timestamp = $known_daemons->{$host}->{timestamp};
1064 # my @clients = keys %{$known_daemons->{$host}->{clients}};
1065 # my $client_string = join(", ", @clients);
1066 # print "$host\n";
1067 # print "\tstatus: $status\n";
1068 # print "\tpasswd: $passwd\n";
1069 # print "\ttimestamp: $timestamp\n";
1070 # print "\tclients: $client_string\n";
1071 #
1072 # }
1073 # $shmkh->shunlock(LOCK_EX);
1074 # print "####################################\n\n";
1075 # return;
1076 #}
1079 #=== FUNCTION ================================================================
1080 # NAME: create_known_daemons_entry
1081 # PARAMETERS: hostname - string - ip address and port of host
1082 # RETURNS: nothing
1083 # DESCRIPTION: nome est omen
1084 #===============================================================================
1085 #sub create_known_daemons_entry {
1086 # my ($hostname) = @_;
1087 # $shmkh->shlock(LOCK_EX);
1088 # $known_daemons->{$hostname} = {};
1089 # $known_daemons->{$hostname}->{status} = "none";
1090 # $known_daemons->{$hostname}->{passwd} = "none";
1091 # $known_daemons->{$hostname}->{timestamp} = "none";
1092 # $known_daemons->{$hostname}->{clients} = {};
1093 # $shmkh->shunlock(LOCK_EX);
1094 # return;
1095 #}
1098 #=== FUNCTION ================================================================
1099 # NAME: update_known_daemons_entry
1100 # PARAMETERS: hostname - string - ip address and port of host (required)
1101 # status - string - (optional)
1102 # passwd - string - (optional)
1103 # client - string - ip address and port of client (optional)
1104 # RETURNS: nothing
1105 # DESCRIPTION: nome est omen and updates each time the timestamp of hostname
1106 #===============================================================================
1107 #sub update_known_daemons_entry {
1108 # my $arg = {
1109 # hostname => undef, status => undef, passwd => undef,
1110 # client => undef,
1111 # @_ };
1112 # my $hostname = $arg->{hostname};
1113 # my $status = $arg->{status};
1114 # my $passwd = $arg->{passwd};
1115 # my $client = $arg->{client};
1116 #
1117 # if (not defined $hostname) {
1118 # daemon_log("ERROR: function add_content2known_daemons is not invoked with requiered parameter 'hostname'", 1);
1119 # return;
1120 # }
1121 #
1122 # my ($seconds, $minutes, $hours, $monthday, $month,
1123 # $year, $weekday, $yearday, $sommertime) = localtime(time);
1124 # $hours = $hours < 10 ? $hours = "0".$hours : $hours;
1125 # $minutes = $minutes < 10 ? $minutes = "0".$minutes : $minutes;
1126 # $seconds = $seconds < 10 ? $seconds = "0".$seconds : $seconds;
1127 # $month+=1;
1128 # $month = $month < 10 ? $month = "0".$month : $month;
1129 # $monthday = $monthday < 10 ? $monthday = "0".$monthday : $monthday;
1130 # $year+=1900;
1131 # my $t = "$year$month$monthday$hours$minutes$seconds";
1132 #
1133 # $shmkh->shlock(LOCK_EX);
1134 # if (defined $status) {
1135 # $known_daemons->{$hostname}->{status} = $status;
1136 # }
1137 # if (defined $passwd) {
1138 # $known_daemons->{$hostname}->{passwd} = $passwd;
1139 # }
1140 # if (defined $client) {
1141 # $known_daemons->{$hostname}->{clients}->{$client} = "";
1142 # }
1143 # $known_daemons->{$hostname}->{timestamp} = $t;
1144 # $shmkh->shunlock(LOCK_EX);
1145 # return;
1146 #}
1149 #==== MAIN = main ==============================================================
1151 # parse commandline options
1152 Getopt::Long::Configure( "bundling" );
1153 GetOptions("h|help" => \&usage,
1154 "c|config=s" => \$cfg_file,
1155 "f|foreground" => \$foreground,
1156 "v|verbose+" => \$verbose,
1157 );
1159 # read and set config parameters
1160 &check_cmdline_param ;
1161 &read_configfile;
1162 &check_pid;
1164 $SIG{CHLD} = 'IGNORE';
1166 # restart daemon log file
1167 if(-e $log_file ) { unlink $log_file }
1168 daemon_log(" ", 1);
1169 daemon_log("$0 started!", 1);
1171 # Just fork, if we"re not in foreground mode
1172 if( ! $foreground ) { $pid = fork(); }
1173 else { $pid = $$; }
1175 # Do something useful - put our PID into the pid_file
1176 if( 0 != $pid ) {
1177 open( LOCK_FILE, ">$pid_file" );
1178 print LOCK_FILE "$pid\n";
1179 close( LOCK_FILE );
1180 if( !$foreground ) { exit( 0 ) };
1181 }
1183 # connect to bus_known_server_db
1184 my @server_col_names = ('hostname', 'status', 'hostkey', 'timestamp', 'clients' );
1185 $bus_known_server_db = GOSA::DBsqlite->new($bus_known_server_file_name);
1186 $bus_known_server_db->create_table('bus_known_server', \@server_col_names);
1189 # detect own ip and mac address
1190 ($bus_ip, $bus_mac_address) = &get_ip_and_mac();
1191 if (not defined $bus_ip) {
1192 die "EXIT: ip address of $0 could not be detected";
1193 }
1194 daemon_log("bus ip address detected: $bus_ip", 1);
1195 daemon_log("bus mac address detected: $bus_mac_address", 1);
1197 # complete addresses
1198 $bus_address = "$bus_ip:$bus_port";
1200 # setup xml parser
1201 $xml = new XML::Simple();
1203 # create cipher object
1204 $bus_cipher = &create_ciphering($bus_passwd);
1205 $bus_address = "$bus_ip:$bus_port";
1207 # create reading and writing vectors
1208 my $rbits = my $wbits = my $ebits = "";
1210 # open the bus socket
1211 if($bus_activ eq "on") {
1212 daemon_log(" ", 1);
1213 $bus = IO::Socket::INET->new(LocalPort => $bus_port,
1214 Type => SOCK_STREAM,
1215 Reuse => 1,
1216 Listen => 20,
1217 ) or die "kann kein TCP-Server an Port $bus_port sein: $@\n";
1218 vec($rbits, fileno $bus, 1) = 1;
1219 vec($wbits, fileno $bus, 1) = 1;
1220 daemon_log ("start bus at $bus_ip:$bus_port", 1);
1221 }
1223 # add bus to known_daemons
1225 #&create_known_daemons_entry($bus_address);
1226 #&update_known_daemons_entry(hostname=>$bus_address, status=>"bus", passwd=>$bus_passwd);
1229 while(1) {
1230 my $nf = select($rbits, $wbits, undef, undef);
1231 # error handling
1232 if($nf < 0 ) {
1233 }
1235 # something is coming in
1236 if(vec $rbits, fileno $bus, 1 ) {
1237 my $client = $bus->accept();
1238 my $other_end = getpeername($client);
1239 if(not defined $other_end) {
1240 daemon_log("Gegenstelle konnte nicht identifiziert werden: $!\n");
1241 } else {
1242 my ($port, $iaddr) = unpack_sockaddr_in($other_end);
1243 my $actual_ip = inet_ntoa($iaddr);
1244 daemon_log("\naccept client from $actual_ip\n", 5);
1245 my $in_msg = &read_from_socket($client);
1246 if(defined $in_msg){
1247 &activating_child($in_msg, $actual_ip);
1248 } else {
1249 daemon_log("cannot read from $actual_ip\n",1);
1250 }
1251 }
1252 close($client);
1253 }
1255 }