a5d50f1d2429bbd1db64e2b82ba3daeb49cdb56c
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, $network_interface);
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_ip" => [\$bus_ip, "0.0.0.0"],
62 "bus_port" => [\$bus_port, "20080"],
63 }
64 );
66 #=== FUNCTION ================================================================
67 # NAME: read_configfile
68 # PARAMETERS: cfg_file - string -
69 # RETURNS: nothing
70 # DESCRIPTION: read cfg_file and set variables
71 #===============================================================================
72 sub read_configfile {
73 my $cfg;
74 if( defined( $cfg_file) && ( length($cfg_file) > 0 )) {
75 if( -r $cfg_file ) {
76 $cfg = Config::IniFiles->new( -file => $cfg_file );
77 } else {
78 print STDERR "Couldn't read config file!";
79 }
80 } else {
81 $cfg = Config::IniFiles->new() ;
82 }
83 foreach my $section (keys %cfg_defaults) {
84 foreach my $param (keys %{$cfg_defaults{ $section }}) {
85 my $pinfo = $cfg_defaults{ $section }{ $param };
86 ${@$pinfo[ 0 ]} = $cfg->val( $section, $param, @$pinfo[ 1 ] );
87 }
88 }
89 }
91 #=== FUNCTION ================================================================
92 # NAME: logging
93 # PARAMETERS: level - string - default 'info'
94 # msg - string -
95 # facility - string - default 'LOG_DAEMON'
96 # RETURNS: nothing
97 # DESCRIPTION: function for logging
98 #===============================================================================
99 sub daemon_log {
100 my( $msg, $level ) = @_;
101 if(not defined $msg) { return }
102 if(not defined $level) { $level = 1 }
103 if(defined $log_file){
104 open(LOG_HANDLE, ">>$log_file");
105 if(not defined open( LOG_HANDLE, ">>$log_file" )) {
106 print STDERR "cannot open $log_file: $!";
107 return }
108 chomp($msg);
109 if($level && $verbose && $level <= $verbose){
110 print LOG_HANDLE $msg."\n";
111 if(defined $foreground) { print $msg."\n" }
112 }
113 }
114 close( LOG_HANDLE );
115 # my ($msg, $level, $facility) = @_;
116 # if(not defined $msg) {return}
117 # if(not defined $level) {$level = "info"}
118 # if(not defined $facility) {$facility = "LOG_DAEMON"}
119 # openlog($0, "pid,cons,", $facility);
120 # syslog($level, $msg);
121 # closelog;
122 # return;
123 }
125 #=== FUNCTION ================================================================
126 # NAME: check_cmdline_param
127 # PARAMETERS: nothing
128 # RETURNS: nothing
129 # DESCRIPTION: validates commandline parameter
130 #===============================================================================
131 sub check_cmdline_param () {
132 my $err_config;
133 my $err_counter = 0;
134 if( not defined( $cfg_file)) {
135 my $cwd = getcwd;
136 my $name = "/etc/gosa-si/bus.conf";
137 $cfg_file = File::Spec->catfile( $cwd, $name );
138 }
139 if( $err_counter > 0 ) {
140 &usage( "", 1 );
141 if( defined( $err_config)) { print STDERR "$err_config\n"}
142 print STDERR "\n";
143 exit( -1 );
144 }
145 }
147 #=== FUNCTION ================================================================
148 # NAME: check_pid
149 # PARAMETERS: nothing
150 # RETURNS: nothing
151 # DESCRIPTION: handels pid processing
152 #===============================================================================
153 sub check_pid {
154 $pid = -1;
155 # Check, if we are already running
156 if( open(LOCK_FILE, "<$pid_file") ) {
157 $pid = <LOCK_FILE>;
158 if( defined $pid ) {
159 chomp( $pid );
160 if( -f "/proc/$pid/stat" ) {
161 my($stat) = `cat /proc/$pid/stat` =~ m/$pid \((.+)\).*/;
162 if( $0 eq $stat ) {
163 close( LOCK_FILE );
164 exit -1;
165 }
166 }
167 }
168 close( LOCK_FILE );
169 unlink( $pid_file );
170 }
172 # create a syslog msg if it is not to possible to open PID file
173 if (not sysopen(LOCK_FILE, $pid_file, O_WRONLY|O_CREAT|O_EXCL, 0644)) {
174 my($msg) = "Couldn't obtain lockfile '$pid_file' ";
175 if (open(LOCK_FILE, '<', $pid_file)
176 && ($pid = <LOCK_FILE>))
177 {
178 chomp($pid);
179 $msg .= "(PID $pid)\n";
180 } else {
181 $msg .= "(unable to read PID)\n";
182 }
183 if( ! ($foreground) ) {
184 openlog( $0, "cons,pid", "daemon" );
185 syslog( "warning", $msg );
186 closelog();
187 }
188 else {
189 print( STDERR " $msg " );
190 }
191 exit( -1 );
192 }
193 }
196 #=== FUNCTION ================================================================
197 # NAME: usage
198 # PARAMETERS: nothing
199 # RETURNS: nothing
200 # DESCRIPTION: print out usage text to STDERR
201 #===============================================================================
202 sub usage {
203 print STDERR << "EOF" ;
204 usage: $0 [-hvf] [-c config]
206 -h : this (help) message
207 -c <file> : config file
208 -f : foreground, process will not be forked to background
209 -v : be verbose (multiple to increase verbosity)
210 EOF
211 print "\n" ;
212 }
215 #=== FUNCTION ================================================================
216 # NAME: sig_int_handler
217 # PARAMETERS: signal - string - signal arose from system
218 # RETURNS: noting
219 # DESCRIPTION: handels tasks to be done befor signal becomes active
220 #===============================================================================
221 sub sig_int_handler {
222 my ($signal) = @_;
223 if($bus){
224 close($bus);
225 print "$bus closed\n";
226 }
227 print "$signal\n";
228 exit(1);
229 }
230 $SIG{INT} = \&sig_int_handler;
232 #=== FUNCTION ================================================================
233 # NAME: get_interface_for_ip
234 # PARAMETERS: ip address (i.e. 192.168.0.1)
235 # RETURNS: array: list of interfaces if ip=0.0.0.0, matching interface if found, undef else
236 # DESCRIPTION: Uses proc fs (/proc/net/dev) to get list of interfaces.
237 #===============================================================================
238 sub get_interface_for_ip {
239 my $result;
240 my $ip= shift;
241 if ($ip && length($ip) > 0) {
242 my @ifs= &get_interfaces();
243 if($ip eq "0.0.0.0") {
244 $result = "all";
245 } else {
246 foreach (@ifs) {
247 my $if=$_;
248 if(get_ip($if) eq $ip) {
249 $result = $if;
250 }
251 }
252 }
253 }
254 return $result;
255 }
257 #=== FUNCTION ================================================================
258 # NAME: get_interfaces
259 # PARAMETERS: none
260 # RETURNS: (list of interfaces)
261 # DESCRIPTION: Uses proc fs (/proc/net/dev) to get list of interfaces.
262 #===============================================================================
263 sub get_interfaces {
264 my @result;
265 my $PROC_NET_DEV= ('/proc/net/dev');
267 open(PROC_NET_DEV, "<$PROC_NET_DEV")
268 or die "Could not open $PROC_NET_DEV";
270 my @ifs = <PROC_NET_DEV>;
272 close(PROC_NET_DEV);
274 # Eat first two line
275 shift @ifs;
276 shift @ifs;
278 chomp @ifs;
279 foreach my $line(@ifs) {
280 my $if= (split /:/, $line)[0];
281 $if =~ s/^\s+//;
282 push @result, $if;
283 }
285 return @result;
286 }
288 #=== FUNCTION ================================================================
289 # NAME: get_mac
290 # PARAMETERS: interface name (i.e. eth0)
291 # RETURNS: (mac address)
292 # DESCRIPTION: Uses ioctl to get mac address directly from system.
293 #===============================================================================
294 sub get_mac {
295 my $ifreq= shift;
296 my $result;
297 if ($ifreq && length($ifreq) > 0) {
298 if($ifreq eq "all") {
299 $result = "00:00:00:00:00:00";
300 } else {
301 my $SIOCGIFHWADDR= 0x8927; # man 2 ioctl_list
303 # A configured MAC Address should always override a guessed value
304 if ($bus_mac_address and length($bus_mac_address) > 0) {
305 return $bus_mac_address;
306 }
308 socket SOCKET, PF_INET, SOCK_DGRAM, getprotobyname('ip')
309 or die "socket: $!";
311 if(ioctl SOCKET, $SIOCGIFHWADDR, $ifreq) {
312 my ($if, $mac)= unpack 'h36 H12', $ifreq;
314 if (length($mac) > 0) {
315 $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])$/;
316 $mac= sprintf("%s:%s:%s:%s:%s:%s", $1, $2, $3, $4, $5, $6);
317 $result = $mac;
318 }
319 }
320 }
321 }
322 return $result;
323 }
325 #=== FUNCTION ================================================================
326 # NAME: get_ip
327 # PARAMETERS: interface name (i.e. eth0)
328 # RETURNS: (ip address)
329 # DESCRIPTION: Uses ioctl to get ip address directly from system.
330 #===============================================================================
331 sub get_ip {
332 my $ifreq= shift;
333 my $result= "";
334 my $SIOCGIFADDR= 0x8915; # man 2 ioctl_list
335 my $proto= getprotobyname('ip');
337 socket SOCKET, PF_INET, SOCK_DGRAM, $proto
338 or die "socket: $!";
340 if(ioctl SOCKET, $SIOCGIFADDR, $ifreq) {
341 my ($if, $sin) = unpack 'a16 a16', $ifreq;
342 my ($port, $addr) = sockaddr_in $sin;
343 my $ip = inet_ntoa $addr;
345 if ($ip && length($ip) > 0) {
346 $result = $ip;
347 }
348 }
350 return $result;
351 }
353 #=== FUNCTION ================================================================
354 # NAME: activating_child
355 # PARAMETERS: msg - string - incoming message
356 # host - string - host from which the incomming message comes
357 # RETURNS: nothing
358 # DESCRIPTION: handels the distribution of incoming messages to working childs
359 #===============================================================================
360 sub activating_child {
361 my ($msg, $host) = @_;
362 my $child = &get_processing_child();
363 my $pipe_wr = $$child{'pipe_wr'};
364 daemon_log("activating: childpid: $$child{'pid'}", 5);
365 print $pipe_wr $msg.".".$host."\n";
366 return;
367 }
370 #=== FUNCTION ================================================================
371 # NAME: get_processing_child
372 # PARAMETERS: nothing
373 # RETURNS: child - hash - holding the process id and the references to the pipe
374 # handles pipe_wr and pipe_rd
375 # DESCRIPTION: handels the forking, reactivating and keeping alive tasks
376 #===============================================================================
377 sub get_processing_child {
378 my $child;
379 # checking %busy_child{pipe_wr} if msg is 'done', then set child from busy to free
380 while(my ($key, $val) = each(%busy_child)) {
381 # check wether process still exists
382 my $exitus_pid = waitpid($key, WNOHANG);
383 if($exitus_pid != 0) {
384 delete $busy_child{$key};
385 daemon_log( "prozess:$key wurde aus busy_child entfernt\n", 5);
386 next;
387 }
389 # check wether process sitll works
390 my $fh = $$val{'pipe_rd'};
391 $fh->blocking(0);
392 my $child_answer;
393 if(not $child_answer = <$fh>) { next }
394 chomp($child_answer);
395 if($child_answer eq "done") {
396 delete $busy_child{$key};
397 $free_child{$key} = $val;
398 }
399 }
401 while(my ($key, $val) = each(%free_child)) {
402 my $exitus_pid = waitpid($key, WNOHANG);
403 if($exitus_pid != 0) {
404 delete $free_child{$key};
405 daemon_log( "prozess:$key wurde aus free_child entfernt\n", 5);
406 }
407 daemon_log("free child:$key\n", 5);
408 }
409 # check @free_child and @busy_child
410 my $free_len = scalar(keys(%free_child));
411 my $busy_len = scalar(keys(%busy_child));
412 daemon_log("free children $free_len, busy children $busy_len\n",5);
414 # if there is a free child, let the child work
415 if($free_len > 0){
416 my @keys = keys(%free_child);
417 $child = $free_child{$keys[0]};
418 if(defined $child) {
419 $busy_child{$$child{'pid'}} = $child ;
420 delete $free_child{$$child{'pid'}};
421 }
422 return $child;
423 }
425 # no free child, try to fork another one
426 if($free_len + $busy_len < $child_max) {
428 daemon_log("not enough children, create a new one\n",5);
430 # New pipes for communication
431 my( $PARENT_wr, $PARENT_rd );
432 my( $CHILD_wr, $CHILD_rd );
433 pipe( $CHILD_rd, $PARENT_wr );
434 pipe( $PARENT_rd, $CHILD_wr );
435 $PARENT_wr->autoflush(1);
436 $CHILD_wr->autoflush(1);
438 ############
439 # fork child
440 ############
441 my $child_pid = fork();
443 #CHILD
444 if($child_pid == 0) {
445 # Close unused pipes
446 close( $CHILD_rd );
447 close( $CHILD_wr );
448 while( 1 ) {
449 my $rbits = "";
450 vec( $rbits, fileno $PARENT_rd , 1 ) = 1;
452 # waiting child_timeout for jobs to do
453 my $nf = select($rbits, undef, undef, $child_timeout);
454 if($nf < 0 ) {
455 # if $nf < 1, error handling
456 die "select(): $!\n";
457 } elsif (! $nf) {
458 # if already child_min childs are alive, then leave loop
459 $free_len = scalar(keys(%free_child));
460 $busy_len = scalar(keys(%busy_child));
461 if($free_len + $busy_len >= $child_min) {
462 last;
463 } else {
464 redo;
465 }
466 }
468 # a job for a child arise
469 if ( vec $rbits, fileno $PARENT_rd, 1 ) {
470 # read everything from pipe
471 my $msg = "";
472 $PARENT_rd->blocking(0);
473 while(1) {
474 my $read = <$PARENT_rd>;
475 if(not defined $read) { last}
476 $msg .= $read;
477 }
479 # forward the job msg to another function
480 &process_incoming_msg($msg);
481 daemon_log("processing of msg finished", 5);
483 # important!!! wait until child says 'done', until then child is set from busy to free
484 print $PARENT_wr "done";
485 redo;
486 }
487 }
488 # childs leaving the loop are allowed to die
489 exit(0);
491 #PARENT
492 } else {
493 # Close unused pipes
494 close( $PARENT_rd );
495 close( $PARENT_wr );
496 # add child to child alive hash
497 my %child_hash = (
498 'pid' => $child_pid,
499 'pipe_wr' => $CHILD_wr,
500 'pipe_rd' => $CHILD_rd,
501 );
503 $child = \%child_hash;
504 $busy_child{$$child{'pid'}} = $child;
505 return $child;
506 }
507 }
508 }
511 #=== FUNCTION ================================================================
512 # NAME: process_incoming_msg
513 # PARAMETERS: crypted_msg - string - incoming crypted message
514 # RETURNS: nothing
515 # DESCRIPTION: handels the proceeded distribution to the appropriated functions
516 #===============================================================================
517 sub process_incoming_msg {
518 my ($crypted_msg) = @_;
519 if(not defined $crypted_msg) {
520 daemon_log("function 'process_incoming_msg': got no msg", 7);
521 return;
522 }
523 $crypted_msg =~ /^([\s\S]*?)\.(\d{1,3}?)\.(\d{1,3}?)\.(\d{1,3}?)\.(\d{1,3}?)$/;
524 $crypted_msg = $1;
525 my $host = sprintf("%s.%s.%s.%s", $2, $3, $4, $5);
527 my $msg;
528 my $msg_hash;
529 my $host_name;
530 my $host_key;
532 # check wether incoming msg is a new msg
533 $host_name = $bus_address;
534 $host_key = $bus_passwd;
535 daemon_log("process_incoming_msg: host_name: $host_name", 7);
536 daemon_log("process_incoming_msg: host_key: $host_key", 7);
537 eval{
538 my $key_cipher = &create_ciphering($host_key);
539 $msg = &decrypt_msg($crypted_msg, $key_cipher);
540 $msg_hash = &transform_msg2hash($msg);
541 };
542 if($@) {
543 daemon_log("process_incoming_msg: deciphering raise error", 7);
544 daemon_log("$@", 8);
545 $msg = undef;
546 $msg_hash = undef;
547 $host_name = undef;
548 $host_key = undef;
549 }
551 # check wether incoming msg is from a bus_known_server
552 if( not defined $msg ) {
553 my $query_res = $bus_known_server_db->select_dbentry( {table=>'bus_known_server'} );
554 while( my ($hit_num, $hit) = each %{ $query_res } ) {
555 $host_name = $hit->{hostname};
556 if( not $host_name =~ "^$host") {
557 next;
558 }
559 $host_key = $hit->{hostkey};
560 daemon_log("process_incoming_msg: host_name: $host_name", 7);
561 daemon_log("process_incoming_msg: host_key: $host_key", 7);
562 eval{
563 my $key_cipher = &create_ciphering($host_key);
564 $msg = &decrypt_msg($crypted_msg, $key_cipher);
565 $msg_hash = &transform_msg2hash($msg);
566 };
567 if($@) {
568 daemon_log("process_incoming_msg: deciphering raise error", 7);
569 daemon_log("$@", 8);
570 $msg = undef;
571 $msg_hash = undef;
572 $host_name = undef;
573 $host_key = undef;
574 } else {
575 last;
576 }
577 }
578 }
580 if( not defined $msg ) {
581 daemon_log("WARNING: bus does not understand the message:", 5);
582 return;
583 }
585 # process incoming msg
586 my $header = @{$msg_hash->{header}}[0];
587 my $source = @{$msg_hash->{source}}[0];
589 daemon_log("header from msg: $header", 1);
590 daemon_log("msg to process:", 5);
591 daemon_log($msg, 5);
593 my @targets = @{$msg_hash->{target}};
594 my $len_targets = @targets;
596 if ($len_targets == 0){
597 daemon_log("ERROR: no target specified for msg $header", 1);
599 } elsif ($len_targets == 1){
600 # we have only one target symbol
601 my $target = $targets[0];
602 daemon_log("msg is for: $target", 7);
604 if($target eq $bus_address) {
605 # msg is for bus
606 if($header eq 'here_i_am'){ &here_i_am($msg_hash)}
607 elsif($header eq 'confirm_new_passwd'){ &confirm_new_passwd($msg_hash)}
608 elsif($header eq 'got_ping') { &got_ping($msg_hash)}
609 elsif($header eq 'ping') { &ping($msg_hash)}
610 elsif($header eq 'who_has') { &who_has($msg_hash)}
611 elsif($header eq 'new_client') { &new_client($msg_hash)}
612 elsif($header eq 'delete_client') { &delete_client($msg_hash)}
614 } elsif ($target eq "*"){
615 # msg is for all server
616 my $query_res = $bus_known_server_db->select_dbentry( {table=>'known_server'} );
617 while( my ($hit_num, $hit) = each %{ $query_res } ) {
618 $host_name = $hit->{hostname};
619 $host_key = $hit->{hostkey};
620 $msg_hash->{target} = [$host_name];
621 &send_msg_hash2address($msg_hash, $host_name, $host_key);
622 }
623 return;
624 }
626 } else {
627 # a list of targets is specified
628 my $target_address;
629 foreach $target_address (@targets) {
631 my $query_res = $bus_known_server_db->select_dbentry( {table=>'known_server', hostname=>$target_address} );
632 if( 1 == keys %{$query_res} ) {
633 $host_key = $query_res->{1}->{hostkey};
634 &send_msg_hash2address($msg_hash, $target_address, $host_key);
635 next;
637 } else {
638 $query_res = $bus_known_server_db->select_dbentry( {table=>'known_server'} );
639 while( my ($hit_num, $hit) = each %{$query_res} ) {
640 my $host_name = $hit->{hostname};
641 my $host_key = $hit->{hostkey};
642 my $clients = $hit->{clients};
643 my @clients = split(/,/, $clients);
644 foreach my $client (@clients) {
645 if( not $client eq $target_address ) {
646 next;
647 }
648 $msg_hash->{target} = [ $target_address ];
649 &send_msg_hash2address($msg_hash, $host_name, $host_key);
650 daemon_log("bus forwards msg $header for client $target_address to server $host_name", 3);
651 last;
652 }
653 }
654 }
655 }
656 }
658 return;
659 }
662 #=== FUNCTION ================================================================
663 # NAME: get_content_of_known_daemons
664 # PARAMETERS:
665 # RETURNS:
666 # DESCRIPTION:
667 #===============================================================================
668 #sub get_content_of_known_daemons {
669 # my ($host, $content) = @_;
670 # return;
671 #}
674 #=== FUNCTION ================================================================
675 # NAME: create_passwd
676 # PARAMETERS: nothing
677 # RETURNS: new_passwd - string
678 # DESCRIPTION: creates a 32 bit long random passwd out of "a".."z","A".."Z",0..9
679 #===============================================================================
680 sub create_passwd {
681 my $new_passwd = "";
682 for(my $i=0; $i<31; $i++) {
683 $new_passwd .= ("a".."z","A".."Z",0..9)[int(rand(62))]
684 }
685 return $new_passwd;
686 }
689 #=== FUNCTION ================================================================
690 # NAME: create_ciphering
691 # PARAMETERS: passwd - string - used to create ciphering
692 # RETURNS: cipher - object
693 # DESCRIPTION: creates a Crypt::Rijndael::MODE_CBC object with passwd as key
694 #===============================================================================
695 #sub create_ciphering {
696 # my ($passwd) = @_;
697 # $passwd = substr(md5_hex("$passwd") x 32, 0, 32);
698 # my $iv = substr(md5_hex('GONICUS GmbH'),0, 16);
699 #
700 # my $my_cipher = Crypt::Rijndael->new($passwd , Crypt::Rijndael::MODE_CBC());
701 # $my_cipher->set_iv($iv);
702 # return $my_cipher;
703 #}
706 #=== FUNCTION ================================================================
707 # NAME: encrypt_msg
708 # PARAMETERS: msg - string - message to encrypt
709 # my_cipher - ref - reference to a Crypt::Rijndael object
710 # RETURNS: crypted_msg - string - crypted message
711 # DESCRIPTION: crypts the incoming message with the Crypt::Rijndael module
712 #===============================================================================
713 #sub encrypt_msg {
714 # my ($msg, $my_cipher) = @_;
715 # if(not defined $my_cipher) { print "no cipher object\n"; }
716 # $msg = "\0"x(16-length($msg)%16).$msg;
717 # my $crypted_msg = $my_cipher->encrypt($msg);
718 # chomp($crypted_msg = &encode_base64($crypted_msg));
719 # return $crypted_msg;
720 #}
723 #=== FUNCTION ================================================================
724 # NAME: decrypt_msg
725 # PARAMETERS: crypted_msg - string - message to decrypt
726 # my_cipher - ref - reference to a Crypt::Rijndael object
727 # RETURNS: msg - string - decrypted message
728 # DESCRIPTION: decrypts the incoming message with the Crypt::Rijndael module
729 #===============================================================================
730 #sub decrypt_msg {
731 # my ($crypted_msg, $my_cipher) = @_ ;
732 # $crypted_msg = &decode_base64($crypted_msg);
733 # my $msg = $my_cipher->decrypt($crypted_msg);
734 # $msg =~ s/^\0*//g;
735 # return $msg;
736 #}
739 #=== FUNCTION ================================================================
740 # NAME: create_xml_hash
741 # PARAMETERS: header - string - message header (required)
742 # source - string - where the message come from (required)
743 # target - string - where the message should go to (required)
744 # [header_value] - string - something usefull (optional)
745 # RETURNS: hash - hash - nomen est omen
746 # DESCRIPTION: creates a key-value hash, all values are stored in a array
747 #===============================================================================
748 #sub create_xml_hash {
749 # my ($header, $source, $target, $header_value) = @_ ;
750 #
751 # if (not defined $header || not defined $source || not defined $target) {
752 # daemon_log("ERROR: create_xml_hash function is invoked with uncompleted parameters", 7);
753 # }
754 #
755 # my $hash = {
756 # header => [$header],
757 # source => [$source],
758 # target => [$target],
759 # $header => [$header_value],
760 # };
761 # #daemon_log("create_xml_hash:", 7),
762 # #chomp(my $tmp = Dumper $hash);
763 # #daemon_log("\t$tmp\n", 7);
764 # return $hash
765 #}
768 #=== FUNCTION ================================================================
769 # NAME: create_xml_string
770 # PARAMETERS: xml_hash - hash - hash from function create_xml_hash
771 # RETURNS: xml_string - string - xml string representation of the hash
772 # DESCRIPTION: transform the hash to a string using XML::Simple module
773 #===============================================================================
774 #sub create_xml_string {
775 # my ($xml_hash) = @_ ;
776 # my $xml_string = $xml->XMLout($xml_hash, RootName => 'xml');
777 # #$xml_string =~ s/[\n]+//g;
778 # return $xml_string;
779 #}
782 #=== FUNCTION ================================================================
783 # NAME: add_content2xml_hash
784 # PARAMETERS: xml_ref - ref - reference to a hash from function create_xml_hash
785 # element - string - key for the hash
786 # content - string - value for the hash
787 # RETURNS: nothing
788 # DESCRIPTION: add key-value pair to xml_ref, if key alread exists, then append value to list
789 #===============================================================================
790 #sub add_content2xml_hash {
791 # my ($xml_ref, $element, $content) = @_;
792 # if(not exists $$xml_ref{$element} ) {
793 # $$xml_ref{$element} = [];
794 # }
795 # my $tmp = $$xml_ref{$element};
796 # push(@$tmp, $content);
797 # return;
798 #}
801 #=== FUNCTION ================================================================
802 # NAME: get_content_from_xml_hash
803 # PARAMETERS: xml_ref - ref - reference of the xml hash
804 # element - string - key of the value you want
805 # RETURNS: value - string - if key is either header, target or source
806 # value - list - for all other keys in xml hash
807 # DESCRIPTION:
808 #===============================================================================
809 #sub get_content_from_xml_hash {
810 # my ($xml_ref, $element) = @_;
811 # my $result = $xml_ref->{$element};
812 # if( $element eq "header" || $element eq "target" || $element eq "source") {
813 # return @$result[0];
814 # }
815 # return @$result;
816 #}
819 #=== FUNCTION ================================================================
820 # NAME: open_socket
821 # PARAMETERS: PeerAddr - string - something like 192.168.1.1 or 192.168.1.1:10000
822 # [PeerPort] - string - necessary if port not appended by PeerAddr
823 # RETURNS: socket - IO::Socket::INET
824 # DESCRIPTION: open a socket to PeerAddr
825 #===============================================================================
826 #sub open_socket {
827 # my ($PeerAddr, $PeerPort) = @_ ;
828 # if(defined($PeerPort)){
829 # $PeerAddr = $PeerAddr.":".$PeerPort;
830 # }
831 # my $socket;
832 # $socket = new IO::Socket::INET(PeerAddr => $PeerAddr ,
833 # Porto => "tcp" ,
834 # Type => SOCK_STREAM,
835 # Reuse => 1,
836 # Timeout => 5,
837 # );
838 # if(not defined $socket) {
839 # return;
840 # }
841 # return $socket;
842 #}
845 #=== FUNCTION ================================================================
846 # NAME: read_from_socket
847 # PARAMETERS: socket - fh - filehandel to read from
848 # RETURNS: result - string - readed characters from socket
849 # DESCRIPTION: reads data from socket in 16 byte steps
850 #===============================================================================
851 sub read_from_socket {
852 my ($socket) = @_;
854 $socket->blocking(1);
855 my $result = <$socket>;
856 $socket->blocking(0);
857 my $part_msg;
858 while ($part_msg = <$socket>) {
859 if (not defined $part_msg) { last; }
860 $result .= $part_msg;
861 }
863 #my $result = "";
864 #my $len = 16;
865 #while($len == 16){
866 # my $char;
867 # $len = sysread($socket, $char, 16);
868 # if($len != 16) { last }
869 # if($len != 16) { last }
870 # $result .= $char;
871 #}
872 return $result;
873 }
876 #=== FUNCTION ================================================================
877 # NAME: send_msg_hash2address
878 # PARAMETERS: msg_hash - hash - xml_hash created with function create_xml_hash
879 # PeerAddr string - socket address to send msg
880 # PeerPort string - socket port, if not included in socket address
881 # RETURNS: nothing
882 # DESCRIPTION: ????
883 #===============================================================================
884 #sub send_msg_hash2address {
885 # my ($msg_hash, $address) = @_ ;
886 #
887 # # fetch header for logging
888 # my $header = &get_content_from_xml_hash($msg_hash, "header");
889 #
890 # # generate xml string
891 # my $msg_xml = &create_xml_string($msg_hash);
892 #
893 # # fetch the appropriated passwd from hash
894 # my $passwd = $known_daemons->{$address}->{passwd};
895 #
896 # # create a ciphering object
897 # my $act_cipher = &create_ciphering($passwd);
898 #
899 # # encrypt xml msg
900 # my $crypted_msg = &encrypt_msg($msg_xml, $act_cipher);
901 #
902 # # open socket
903 # my $socket = &open_socket($address);
904 # if(not defined $socket){
905 # daemon_log("ERROR: cannot send '$header'-msg to $address , server not reachable", 1);
906 # return;
907 # }
908 #
909 # # send xml msg
910 # print $socket $crypted_msg."\n";
911 #
912 # close $socket;
913 # daemon_log("send '$header'-msg to $address", 5);
914 # daemon_log("crypted_msg:\n\t$crypted_msg", 7);
915 # return;
916 #}
919 #=== FUNCTION ================================================================
920 # NAME: send_msg_hash2all
921 # PARAMETERS: msg_hash - hash - xml_hash created with function create_xml_hash
922 # RETURNS: nothing
923 # DESCRIPTION: send msg_hash to all registered daemons
924 #===============================================================================
925 #sub send_msg_hash2all {
926 # my ($msg_hash) = @_;
927 #
928 # # fetch header for logging
929 # my $header = &get_content_from_xml_hash($msg_hash, "header");
930 #
931 # # generate xml string
932 # my $msg_xml = &create_xml_string($msg_hash);
933 #
934 # # fetch a list of all target addresses
935 # my @targets = keys(%$known_daemons);
936 #
937 # # itterates through the list an send each the msg
938 # foreach my $target (@targets) {
939 # if($target eq $bus_address) {next}; # do not send msg to bus
940 #
941 # # fetch the appropriated passwd
942 # my $passwd = $known_daemons->{$target}->{passwd};
943 #
944 # # create ciphering object
945 # my $act_cipher = &create_ciphering($passwd);
946 #
947 # # encrypt xml msg
948 # my $crypted_msg = &encrypt_msg($msg_xml, $act_cipher);
949 #
950 # # open socket
951 # my $socket = &open_socket($target);
952 # if(not defined $socket){
953 # daemon_log("ERROR: cannot open socket to $target , server not reachable", 1);
954 # &update_known_daemons_entry(hostname=>$target, status=>"down");
955 # next;
956 # }
957 #
958 # # send xml msg
959 # print $socket $crypted_msg."\n";
960 #
961 # close $socket;
962 # daemon_log("send '$header'-msg to $target", 5);
963 # daemon_log("crypted_msg:\n\t$crypted_msg", 7);
964 # }
965 # return;
966 #}
969 #=== FUNCTION ================================================================
970 # NAME: here_i_am
971 # PARAMETERS: msg_hash - hash - hash from function create_xml_hash
972 # RETURNS: nothing
973 # DESCRIPTION: process the incoming msg 'here_i_am'
974 #===============================================================================
975 sub here_i_am {
976 my ($msg_hash) = @_ ;
977 my $source = @{$msg_hash->{source}}[0];;
979 my $new_key = &create_passwd();
981 # create bus_known_server entry
982 my $add_hash = {
983 table=>"bus_known_server",
984 primkey=>"hostname",
985 hostname=>$source,
986 status=>"registered",
987 hostkey=>$bus_passwd,
988 clients=>"",
989 };
990 $bus_known_server_db->add_dbentry($add_hash);
992 # create outgoing msg
993 my $out_hash = &create_xml_hash("new_passwd", $bus_address, $source, $new_key);
994 &send_msg_hash2address($out_hash, $source, $bus_passwd);
996 # change hostkey, reason
997 my $update_hash = { table=>'bus_known_server' };
998 $update_hash->{where} = [ { hostname=>[$source] } ];
999 $update_hash->{update} = [ { hostkey=>[$new_key] } ];
1000 $bus_known_server_db->update_dbentry($update_hash);
1002 return;
1003 }
1006 #=== FUNCTION ================================================================
1007 # NAME: confirm_new_passwd
1008 # PARAMETERS: msg_hash - hash - hash from function create_xml_hash
1009 # RETURNS: nothing
1010 # DESCRIPTION: process this incoming message
1011 #===============================================================================
1012 sub confirm_new_passwd {
1013 my ($msg_hash) = @_ ;
1014 my $source = @{$msg_hash->{source}}[0];
1016 my $update_hash = { table=>'bus_known_server' };
1017 $update_hash->{where} = [ { hostname=>[$source] } ];
1018 $update_hash->{update} = [ { status=>['key_confirmed'] } ];
1019 $bus_known_server_db->update_dbentry($update_hash);
1021 return;
1022 }
1025 #=== FUNCTION ================================================================
1026 # NAME: ping
1027 # PARAMETERS: msg_hash - hash - hash from function create_xml_hash
1028 # RETURNS: nothing
1029 # DESCRIPTION: process this incoming message
1030 #===============================================================================
1031 sub ping {
1032 my ($msg_hash) = @_ ;
1033 my $header = @{$msg_hash->{header}}[0];
1034 my $source = @{$msg_hash->{source}}[0];
1036 my $update_hash = { table=>'bus_known_server',
1037 where=> [ { hostname=>[$source] } ],
1038 update=> [ { status=>$header } ],
1039 };
1040 $bus_known_server_db->update_dbentry($update_hash);
1042 my $out_hash = &create_xml_hash("got_ping", $bus_address, $source);
1044 my $res = $bus_known_server_db->select_dbentry( { table=>'bus_known_server', hostname=>$source } );
1045 my $hostkey = $res->{1}->{hostkey};
1046 &send_msg_hash2address($out_hash, $source, $hostkey);
1048 return;
1049 }
1052 #=== FUNCTION ================================================================
1053 # NAME: make ping
1054 # PARAMETERS: address - string - address which should be pinged
1055 # RETURNS: nothing
1056 # DESCRIPTION: send ping message to address
1057 #===============================================================================
1058 #sub make_ping {
1059 # my ($address) = @_;
1060 # daemon_log("ping:$address\n", 1);
1061 # my $out_hash = &create_xml_hash("ping", "$bus_ip:$bus_port", $address);
1062 # &send_msg_hash2address($out_hash, $address);
1063 # return;
1064 #}
1067 #=== FUNCTION ================================================================
1068 # NAME: got_ping
1069 # PARAMETERS: msg_hash - hash - hash from function create_xml_hash
1070 # RETURNS: nothing
1071 # DESCRIPTION: process this incoming message
1072 #===============================================================================
1073 sub got_ping {
1074 my ($msg_hash) = @_;
1075 my $source = @{$msg_hash->{source}}[0];
1077 my $update_hash = { table=>'bus_known_server',
1078 where=> [ { hostname=>[$source] } ],
1079 update=> [ { status=>'got_ping' } ],
1080 };
1081 $bus_known_server_db->update_dbentry($update_hash);
1083 return;
1084 }
1087 #=== FUNCTION ================================================================
1088 # NAME: new_client
1089 # PARAMETERS: msg_hash - hash - hash from function create_xml_hash
1090 # RETURNS: nothing
1091 # DESCRIPTION: process this incoming message
1092 #===============================================================================
1093 sub new_client {
1094 my ($msg_hash) = @_ ;
1095 my $source = @{$msg_hash->{source}}[0];
1096 my $header = @{$msg_hash->{header}}[0];
1097 my $new_client = @{$msg_hash->{$header}}[0];
1099 my $res = $bus_known_server_db->select_dbentry( { table=>'bus_known_server', hostname=>$source } );
1100 my $clients = $res->{1}->{clients};
1102 # if host has alread more clients, than just append
1103 if( length($clients) != 0 ) {
1104 $clients .= ",$new_client";
1105 } else {
1106 $clients = $new_client;
1107 }
1109 my $update_hash = { table=>'bus_known_server',
1110 where=>[ {hostname=>[$source] } ],
1111 update=>[ {clients=>[$clients] } ],
1112 };
1114 $bus_known_server_db->update_dbentry( $update_hash );
1115 return;
1116 }
1119 #=== FUNCTION ================================================================
1120 # NAME: delete_client
1121 # PARAMETERS: msg_hash - hash - hash from function create_xml_hash
1122 # RETURNS: nothing
1123 # DESCRIPTION: process this incoming message
1124 #===============================================================================
1125 #sub delete_client {
1126 # my ($msg_hash) = @_ ;
1127 # my $source = &get_content_from_xml_hash($msg_hash, "source");
1128 # my $header = &get_content_from_xml_hash($msg_hash, "header");
1129 # my $del_client = (&get_content_from_xml_hash($msg_hash, $header))[0];
1130 #
1131 # if (not exists $known_daemons->{$source}->{$del_client}) {
1132 # daemon_log
1133 # }
1134 # delete $known_daemons->{$source}->{$del_client};
1135 #
1136 # return;
1137 #}
1140 #=== FUNCTION ================================================================
1141 # NAME: print_known_daemons_hash
1142 # PARAMETERS: nothing
1143 # RETURNS: nothing
1144 # DESCRIPTION: nome est omen
1145 #===============================================================================
1146 #sub print_known_daemons_hash {
1147 # my ($tmp) = @_;
1148 # print "####################################\n";
1149 # print "# status of known_daemons\n";
1150 # my $hosts;
1151 # my $host_hash;
1152 # $shmkh->shlock(LOCK_EX);
1153 # my @hosts = keys %$known_daemons;
1154 # foreach my $host (@hosts) {
1155 # my $status = $known_daemons->{$host}->{status} ;
1156 # my $passwd = $known_daemons->{$host}->{passwd};
1157 # my $timestamp = $known_daemons->{$host}->{timestamp};
1158 # my @clients = keys %{$known_daemons->{$host}->{clients}};
1159 # my $client_string = join(", ", @clients);
1160 # print "$host\n";
1161 # print "\tstatus: $status\n";
1162 # print "\tpasswd: $passwd\n";
1163 # print "\ttimestamp: $timestamp\n";
1164 # print "\tclients: $client_string\n";
1165 #
1166 # }
1167 # $shmkh->shunlock(LOCK_EX);
1168 # print "####################################\n\n";
1169 # return;
1170 #}
1173 #=== FUNCTION ================================================================
1174 # NAME: create_known_daemons_entry
1175 # PARAMETERS: hostname - string - ip address and port of host
1176 # RETURNS: nothing
1177 # DESCRIPTION: nome est omen
1178 #===============================================================================
1179 #sub create_known_daemons_entry {
1180 # my ($hostname) = @_;
1181 # $shmkh->shlock(LOCK_EX);
1182 # $known_daemons->{$hostname} = {};
1183 # $known_daemons->{$hostname}->{status} = "none";
1184 # $known_daemons->{$hostname}->{passwd} = "none";
1185 # $known_daemons->{$hostname}->{timestamp} = "none";
1186 # $known_daemons->{$hostname}->{clients} = {};
1187 # $shmkh->shunlock(LOCK_EX);
1188 # return;
1189 #}
1192 #=== FUNCTION ================================================================
1193 # NAME: update_known_daemons_entry
1194 # PARAMETERS: hostname - string - ip address and port of host (required)
1195 # status - string - (optional)
1196 # passwd - string - (optional)
1197 # client - string - ip address and port of client (optional)
1198 # RETURNS: nothing
1199 # DESCRIPTION: nome est omen and updates each time the timestamp of hostname
1200 #===============================================================================
1201 #sub update_known_daemons_entry {
1202 # my $arg = {
1203 # hostname => undef, status => undef, passwd => undef,
1204 # client => undef,
1205 # @_ };
1206 # my $hostname = $arg->{hostname};
1207 # my $status = $arg->{status};
1208 # my $passwd = $arg->{passwd};
1209 # my $client = $arg->{client};
1210 #
1211 # if (not defined $hostname) {
1212 # daemon_log("ERROR: function add_content2known_daemons is not invoked with requiered parameter 'hostname'", 1);
1213 # return;
1214 # }
1215 #
1216 # my ($seconds, $minutes, $hours, $monthday, $month,
1217 # $year, $weekday, $yearday, $sommertime) = localtime(time);
1218 # $hours = $hours < 10 ? $hours = "0".$hours : $hours;
1219 # $minutes = $minutes < 10 ? $minutes = "0".$minutes : $minutes;
1220 # $seconds = $seconds < 10 ? $seconds = "0".$seconds : $seconds;
1221 # $month+=1;
1222 # $month = $month < 10 ? $month = "0".$month : $month;
1223 # $monthday = $monthday < 10 ? $monthday = "0".$monthday : $monthday;
1224 # $year+=1900;
1225 # my $t = "$year$month$monthday$hours$minutes$seconds";
1226 #
1227 # $shmkh->shlock(LOCK_EX);
1228 # if (defined $status) {
1229 # $known_daemons->{$hostname}->{status} = $status;
1230 # }
1231 # if (defined $passwd) {
1232 # $known_daemons->{$hostname}->{passwd} = $passwd;
1233 # }
1234 # if (defined $client) {
1235 # $known_daemons->{$hostname}->{clients}->{$client} = "";
1236 # }
1237 # $known_daemons->{$hostname}->{timestamp} = $t;
1238 # $shmkh->shunlock(LOCK_EX);
1239 # return;
1240 #}
1243 #==== MAIN = main ==============================================================
1245 # parse commandline options
1246 Getopt::Long::Configure( "bundling" );
1247 GetOptions("h|help" => \&usage,
1248 "c|config=s" => \$cfg_file,
1249 "f|foreground" => \$foreground,
1250 "v|verbose+" => \$verbose,
1251 );
1253 # read and set config parameters
1254 &check_cmdline_param ;
1255 &read_configfile;
1256 &check_pid;
1258 $SIG{CHLD} = 'IGNORE';
1260 # restart daemon log file
1261 if(-e $log_file ) { unlink $log_file }
1262 daemon_log(" ", 1);
1263 daemon_log("$0 started!", 1);
1265 # forward error messages to logfile
1266 if( ! $foreground ) {
1267 open(STDERR, '>>', $log_file);
1268 open(STDOUT, '>>', $log_file);
1269 }
1271 # Just fork, if we"re not in foreground mode
1272 if( ! $foreground ) {
1273 chdir '/' or die "Can't chdir to /: $!";
1274 $pid = fork;
1275 setsid or die "Can't start a new session: $!";
1276 umask 0;
1277 }
1279 else { $pid = $$; }
1281 # Do something useful - put our PID into the pid_file
1282 if( 0 != $pid ) {
1283 open( LOCK_FILE, ">$pid_file" );
1284 print LOCK_FILE "$pid\n";
1285 close( LOCK_FILE );
1286 if( !$foreground ) { exit( 0 ) };
1287 }
1289 # connect to bus_known_server_db
1290 my @server_col_names = ('hostname', 'status', 'hostkey', 'timestamp', 'clients' );
1291 $bus_known_server_db = GOSA::DBsqlite->new($bus_known_server_file_name);
1292 $bus_known_server_db->create_table('bus_known_server', \@server_col_names);
1295 # detect own ip and mac address
1296 $network_interface= &get_interface_for_ip($bus_ip);
1297 $bus_mac_address= &get_mac($network_interface);
1299 daemon_log("bus ip address detected: $bus_ip", 1);
1300 daemon_log("bus mac address detected: $bus_mac_address", 1);
1302 # complete addresses
1303 $bus_address = "$bus_ip:$bus_port";
1305 # setup xml parser
1306 $xml = new XML::Simple();
1308 # create cipher object
1309 $bus_cipher = &create_ciphering($bus_passwd);
1310 $bus_address = "$bus_ip:$bus_port";
1312 # create reading and writing vectors
1313 my $rbits = my $wbits = my $ebits = "";
1315 # open the bus socket
1316 if($bus_activ eq "on") {
1317 daemon_log(" ", 1);
1318 $bus = IO::Socket::INET->new(LocalPort => $bus_port,
1319 Type => SOCK_STREAM,
1320 Reuse => 1,
1321 Listen => 20,
1322 ) or die "kann kein TCP-Server an Port $bus_port sein: $@\n";
1323 vec($rbits, fileno $bus, 1) = 1;
1324 vec($wbits, fileno $bus, 1) = 1;
1325 daemon_log ("start bus at $bus_ip:$bus_port", 1);
1326 }
1328 # add bus to known_daemons
1330 #&create_known_daemons_entry($bus_address);
1331 #&update_known_daemons_entry(hostname=>$bus_address, status=>"bus", passwd=>$bus_passwd);
1334 while(1) {
1335 my $nf = select($rbits, $wbits, undef, undef);
1336 # error handling
1337 if($nf < 0 ) {
1338 }
1340 # something is coming in
1341 if(vec $rbits, fileno $bus, 1 ) {
1342 my $client = $bus->accept();
1343 my $other_end = getpeername($client);
1344 if(not defined $other_end) {
1345 daemon_log("Gegenstelle konnte nicht identifiziert werden: $!\n");
1346 } else {
1347 my ($port, $iaddr) = unpack_sockaddr_in($other_end);
1348 my $actual_ip = inet_ntoa($iaddr);
1349 daemon_log("\naccept client from $actual_ip\n", 5);
1350 my $in_msg = &read_from_socket($client);
1351 if(defined $in_msg){
1352 &activating_child($in_msg, $actual_ip);
1353 } else {
1354 daemon_log("cannot read from $actual_ip\n",1);
1355 }
1356 }
1357 close($client);
1358 }
1360 }