fe07d32f42669cbd8ac6b09a7d003e2cde713f3f
1 #!/usr/bin/perl
2 #===============================================================================
3 #
4 # FILE: gosa-server
5 #
6 # USAGE: gosa-si-client
7 #
8 # DESCRIPTION:
9 #
10 # OPTIONS: ---
11 # REQUIREMENTS: libnetaddr-ip-perl
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 );
27 use File::Basename;
29 use Fcntl;
30 use IO::Socket::INET;
31 use Crypt::Rijndael;
32 use MIME::Base64;
33 use Digest::MD5 qw(md5 md5_hex md5_base64);
34 use XML::Simple;
35 use Data::Dumper;
36 use Sys::Syslog qw( :DEFAULT setlogsock);
37 use File::Spec;
38 use Cwd;
39 use NetAddr::IP;
40 use GOSA::GosaSupportDaemon;
43 my ($cfg_file, %cfg_defaults, $foreground, $verbose, $pid_file, $procid, $pid, $log_file);
44 my ($server_address, $server_ip, $server_port, $server_domain, $server_passwd, $server_cipher, $server_timeout);
45 my ($client_address, $client_ip, $client_port, $client_mac_address, $network_interface, $ldap_config, $pam_config, $nss_config);
46 my ($input_socket, $rbits, $wbits, $ebits, $xml, $known_hosts, $ldap_enabled);
47 my (@events);
49 # default variables
50 my $event_dir = "/usr/lib/gosa-si/client/events";
51 $known_hosts = {};
52 $foreground = 0 ;
53 %cfg_defaults =
54 ("general" =>
55 {"log_file" => [\$log_file, "/var/run/".$0.".log"],
56 "pid_file" => [\$pid_file, "/var/run/".$0.".pid"],
57 },
58 "client" =>
59 {"client_port" => [\$client_port, "20083"],
60 "client_ip" => [\$client_ip, "0.0.0.0"],
61 "ldap" => [\$ldap_enabled, 1],
62 "ldap_config" => [\$ldap_config, "/etc/ldap/ldap.conf"],
63 "pam_config" => [\$pam_config, "/etc/pam_ldap.conf"],
64 "nss_config" => [\$nss_config, "/etc/libnss_ldap.conf"],
65 },
66 "server" =>
67 {"server_ip" => [\$server_ip, ""],
68 "server_port" => [\$server_port, "20081"],
69 "server_passwd" => [\$server_passwd, ""],
70 "server_timeout" => [\$server_timeout, 10],
71 "server_domain" => [\$server_domain, ""],
72 },
73 );
76 #=== FUNCTION ================================================================
77 # NAME: read_configfile
78 # PARAMETERS: cfg_file - string -
79 # RETURNS:
80 # DESCRIPTION:
81 #===============================================================================
82 sub read_configfile {
83 my $cfg;
84 if( defined( $cfg_file) && ( length($cfg_file) > 0 )) {
85 if( -r $cfg_file ) {
86 $cfg = Config::IniFiles->new( -file => $cfg_file );
87 } else {
88 print STDERR "Couldn't read config file!";
89 }
90 } else {
91 $cfg = Config::IniFiles->new() ;
92 }
93 foreach my $section (keys %cfg_defaults) {
94 foreach my $param (keys %{$cfg_defaults{ $section }}) {
95 my $pinfo = $cfg_defaults{ $section }{ $param };
96 ${@$pinfo[ 0 ]} = $cfg->val( $section, $param, @$pinfo[ 1 ] );
97 }
98 }
99 }
102 #=== FUNCTION ================================================================
103 # NAME: logging
104 # PARAMETERS: level - string - default 'info'
105 # msg - string -
106 # facility - string - default 'LOG_DAEMON'
107 # RETURNS:
108 # DESCRIPTION:
109 #===============================================================================
110 sub daemon_log {
111 my( $msg, $level ) = @_;
112 if(not defined $msg) { return }
113 if(not defined $level) { $level = 1 }
114 if(defined $log_file){
115 open(LOG_HANDLE, ">>$log_file");
116 if(not defined open( LOG_HANDLE, ">>$log_file" )) {
117 print STDERR "cannot open $log_file: $!";
118 return }
119 chomp($msg);
120 if($level <= $verbose){
121 print LOG_HANDLE $msg."\n";
122 if(defined $foreground) { print $msg."\n" }
123 }
124 }
125 close( LOG_HANDLE );
126 # my ($msg, $level, $facility) = @_;
127 # if(not defined $msg) {return}
128 # if(not defined $level) {$level = "info"}
129 # if(not defined $facility) {$facility = "LOG_DAEMON"}
130 # openlog($0, "pid,cons,", $facility);
131 # syslog($level, $msg);
132 # closelog;
133 # return;
134 }
137 #=== FUNCTION ================================================================
138 # NAME: check_cmdline_param
139 # PARAMETERS:
140 # RETURNS:
141 # DESCRIPTION:
142 #===============================================================================
143 sub check_cmdline_param () {
144 my $err_config;
145 my $err_counter = 0;
146 if(not defined($cfg_file)) {
147 $cfg_file = "/etc/gosa-si/client.conf";
148 if(! -r $cfg_file) {
149 $err_config = "please specify a config file";
150 $err_counter += 1;
151 }
152 }
153 if( $err_counter > 0 ) {
154 &usage( "", 1 );
155 if( defined( $err_config)) { print STDERR "$err_config\n"}
156 print STDERR "\n";
157 exit( -1 );
158 }
159 }
162 #=== FUNCTION ================================================================
163 # NAME: check_pid
164 # PARAMETERS:
165 # RETURNS:
166 # DESCRIPTION:
167 #===============================================================================
168 sub check_pid {
169 $pid = -1;
170 # Check, if we are already running
171 if( open(LOCK_FILE, "<$pid_file") ) {
172 $pid = <LOCK_FILE>;
173 if( defined $pid ) {
174 chomp( $pid );
175 if( -f "/proc/$pid/stat" ) {
176 my($stat) = `cat /proc/$pid/stat` =~ m/$pid \((.+)\).*/;
177 if( $0 eq $stat ) {
178 close( LOCK_FILE );
179 exit -1;
180 }
181 }
182 }
183 close( LOCK_FILE );
184 unlink( $pid_file );
185 }
187 # create a syslog msg if it is not to possible to open PID file
188 if (not sysopen(LOCK_FILE, $pid_file, O_WRONLY|O_CREAT|O_EXCL, 0644)) {
189 my($msg) = "Couldn't obtain lockfile '$pid_file' ";
190 if (open(LOCK_FILE, '<', $pid_file)
191 && ($pid = <LOCK_FILE>))
192 {
193 chomp($pid);
194 $msg .= "(PID $pid)\n";
195 } else {
196 $msg .= "(unable to read PID)\n";
197 }
198 if( ! ($foreground) ) {
199 openlog( $0, "cons,pid", "daemon" );
200 syslog( "warning", $msg );
201 closelog();
202 }
203 else {
204 print( STDERR " $msg " );
205 }
206 exit( -1 );
207 }
208 }
210 #=== FUNCTION ================================================================
211 # NAME: get_interface_for_ip
212 # PARAMETERS: ip address (i.e. 192.168.0.1)
213 # RETURNS: array: list of interfaces if ip=0.0.0.0, matching interface if found, undef else
214 # DESCRIPTION: Uses proc fs (/proc/net/dev) to get list of interfaces.
215 #===============================================================================
216 sub get_interface_for_ip {
217 my $result;
218 my $ip= shift;
219 if ($ip && length($ip) > 0) {
220 my @ifs= &get_interfaces();
221 if($ip eq "0.0.0.0") {
222 $result = "all";
223 } else {
224 foreach (@ifs) {
225 my $if=$_;
226 if(get_ip($if) eq $ip) {
227 $result = $if;
228 last;
229 }
230 }
231 }
232 }
233 return $result;
234 }
236 #=== FUNCTION ================================================================
237 # NAME: get_interfaces
238 # PARAMETERS: none
239 # RETURNS: (list of interfaces)
240 # DESCRIPTION: Uses proc fs (/proc/net/dev) to get list of interfaces.
241 #===============================================================================
242 sub get_interfaces {
243 my @result;
244 my $PROC_NET_DEV= ('/proc/net/dev');
246 open(PROC_NET_DEV, "<$PROC_NET_DEV")
247 or die "Could not open $PROC_NET_DEV";
249 my @ifs = <PROC_NET_DEV>;
251 close(PROC_NET_DEV);
253 # Eat first two line
254 shift @ifs;
255 shift @ifs;
257 chomp @ifs;
258 foreach my $line(@ifs) {
259 my $if= (split /:/, $line)[0];
260 $if =~ s/^\s+//;
261 push @result, $if;
262 }
264 return @result;
265 }
267 #=== FUNCTION ================================================================
268 # NAME: get_mac
269 # PARAMETERS: interface name (i.e. eth0)
270 # RETURNS: (mac address)
271 # DESCRIPTION: Uses ioctl to get mac address directly from system.
272 #===============================================================================
273 sub get_mac {
274 my $ifreq= shift;
275 my $result;
276 if ($ifreq && length($ifreq) > 0) {
277 if($ifreq eq "all") {
278 if(defined($server_ip)) {
279 $result = &get_local_mac_for_remote_ip($server_ip);
280 } else {
281 $result = "00:00:00:00:00:00";
282 }
283 } else {
284 my $SIOCGIFHWADDR= 0x8927; # man 2 ioctl_list
286 # A configured MAC Address should always override a guessed value
287 if ($client_mac_address and length($client_mac_address) > 0) {
288 $result= $client_mac_address;
289 }
291 socket SOCKET, PF_INET, SOCK_DGRAM, getprotobyname('ip')
292 or die "socket: $!";
294 if(ioctl SOCKET, $SIOCGIFHWADDR, $ifreq) {
295 my ($if, $mac)= unpack 'h36 H12', $ifreq;
297 if (length($mac) > 0) {
298 $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])$/;
299 $mac= sprintf("%s:%s:%s:%s:%s:%s", $1, $2, $3, $4, $5, $6);
300 $result = $mac;
301 }
302 }
303 }
304 }
305 return $result;
306 }
308 #=== FUNCTION ================================================================
309 # NAME: get_ip
310 # PARAMETERS: interface name (i.e. eth0)
311 # RETURNS: (ip address)
312 # DESCRIPTION: Uses ioctl to get ip address directly from system.
313 #===============================================================================
314 sub get_ip {
315 my $ifreq= shift;
316 my $result= "";
317 my $SIOCGIFADDR= 0x8915; # man 2 ioctl_list
318 my $proto= getprotobyname('ip');
320 socket SOCKET, PF_INET, SOCK_DGRAM, $proto
321 or die "socket: $!";
323 if(ioctl SOCKET, $SIOCGIFADDR, $ifreq) {
324 my ($if, $sin) = unpack 'a16 a16', $ifreq;
325 my ($port, $addr) = sockaddr_in $sin;
326 my $ip = inet_ntoa $addr;
328 if ($ip && length($ip) > 0) {
329 $result = $ip;
330 }
331 }
333 return $result;
334 }
336 #=== FUNCTION ================================================================
337 # NAME: get_local_mac_for_remote_ip
338 # PARAMETERS: none (takes server_ip from global variable)
339 # RETURNS: (ip address from interface that is used for communication)
340 # DESCRIPTION: Uses ioctl to get routing table from system, checks which entry
341 # matches (defaultroute last).
342 #===============================================================================
343 sub get_local_mac_for_remote_ip {
344 my $ifreq= shift;
345 my $result= "00:00:00:00:00:00";
346 my $PROC_NET_ROUTE= ('/proc/net/route');
348 open(PROC_NET_ROUTE, "<$PROC_NET_ROUTE")
349 or die "Could not open $PROC_NET_ROUTE";
351 my @ifs = <PROC_NET_ROUTE>;
353 close(PROC_NET_ROUTE);
355 # Eat header line
356 shift @ifs;
357 chomp @ifs;
358 foreach my $line(@ifs) {
359 my ($Iface,$Destination,$Gateway,$Flags,$RefCnt,$Use,$Metric,$Mask,$MTU,$Window,$IRTT)=split(/\s/, $line);
360 my $destination;
361 my $mask;
362 my ($d,$c,$b,$a)=unpack('a2 a2 a2 a2', $Destination);
363 $destination= sprintf("%d.%d.%d.%d", hex($a), hex($b), hex($c), hex($d));
364 ($d,$c,$b,$a)=unpack('a2 a2 a2 a2', $Mask);
365 $mask= sprintf("%d.%d.%d.%d", hex($a), hex($b), hex($c), hex($d));
366 if(new NetAddr::IP($server_ip)->within(new NetAddr::IP($destination, $mask))) {
367 # destination matches route, save mac and exit
368 $result= &get_mac($Iface);
369 last;
370 }
371 }
374 return $result;
375 }
377 #=== FUNCTION ================================================================
378 # NAME: usage
379 # PARAMETERS:
380 # RETURNS:
381 # DESCRIPTION:
382 #===============================================================================
383 sub usage {
384 my( $text, $help ) = @_;
385 $text = undef if( "h" eq $text );
386 (defined $text) && print STDERR "\n$text\n";
387 if( (defined $help && $help) || (!defined $help && !defined $text) ) {
388 print STDERR << "EOF" ;
389 usage: $0 [-hvf] [-c config]
391 -h : this (help) message
392 -c <file> : config file
393 -f : foreground, process will not be forked to background
394 -v : be verbose (multiple to increase verbosity)
395 EOF
396 }
397 print "\n" ;
398 }
400 #=== FUNCTION ================================================================
401 # NAME: get_server_addresses
402 # PARAMETERS:
403 # RETURNS:
404 # DESCRIPTION:
405 #===============================================================================
406 sub get_server_addresses {
407 my $domain= shift;
408 my @result;
409 my $dig_cmd= 'dig +nocomments srv _gosad._tcp.'.$domain;
411 my $output= `$dig_cmd 2>&1`;
412 open (PIPE, "$dig_cmd 2>&1 |");
413 while(<PIPE>) {
414 chomp $_;
415 # If it's not a comment
416 if($_ =~ m/^[^;]/) {
417 my @matches= split /\s+/;
419 # Push hostname with port
420 if($matches[3] eq 'SRV') {
421 push @result, $matches[7].':'.$matches[6];
422 } elsif ($matches[3] eq 'A') {
423 my $i=0;
425 # Substitute the hostname with the ip address of the matching A record
426 foreach my $host (@result) {
427 if ((split /\:/, $host)[0] eq $matches[0]) {
428 $result[$i]= $matches[4].':'.(split /\:/, $host)[1];
429 }
430 $i++;
431 }
432 }
433 }
434 }
435 close(PIPE);
436 return @result;
437 }
440 #=== FUNCTION ================================================================
441 # NAME: register_at_server
442 # PARAMETERS:
443 # RETURNS:
444 # DESCRIPTION:
445 #===============================================================================
446 sub register_at_server {
447 my ($tmp) = @_;
449 # create new passwd and ciphering object for client-server communication
450 my $new_server_passwd = &create_passwd();
451 my $new_server_cipher;
453 # detect all client accepted events
454 opendir(DIR, $event_dir)
455 or daemon_log("cannot find directory $event_dir!\ngosa-si-client starts without any accepting events!", 1);
456 my $file_name;
457 my @events_list = ();
458 while(defined($file_name = readdir(DIR))){
459 if ($file_name eq "." || $file_name eq "..") {
460 next;
461 }
462 push(@events_list, $file_name);
463 }
464 my $events = join(",", @events_list);
465 daemon_log("found events: $events", 1);
467 # fill in all possible servers
468 my @servers;
469 if (defined $server_domain) {
470 my @tmp_servers = &get_server_addresses($server_domain);
471 foreach my $server (@tmp_servers) { unshift(@servers, $server); }
472 }
473 # add server address from config file at first position of server list
474 if (defined $server_address) {
475 unshift(@servers, $server_address);
476 }
477 daemon_log("found servers in configuration file and via DNS:", 5);
478 foreach my $server (@servers) {
479 daemon_log("\t$server", 5);
480 }
482 my ($rout, $wout, $reg_server);
483 foreach my $server (@servers) {
485 # create msg hash
486 my $register_hash = &create_xml_hash("here_i_am", $client_address, $server);
487 &add_content2xml_hash($register_hash, "new_passwd", $new_server_passwd);
488 &add_content2xml_hash($register_hash, "mac_address", $client_mac_address);
489 &add_content2xml_hash($register_hash, "events", $events);
491 # send xml hash to server with general server passwd
492 my $answer = &send_msg_hash2address($register_hash, $server, $server_passwd);
494 if ($answer != 0) { next; }
496 # waiting for response
497 daemon_log("waiting for response...\n", 5);
498 my $nf = select($rout=$rbits, $wout=$wbits, undef, $server_timeout);
500 # something is coming in
501 if(vec $rout, fileno $input_socket, 1) {
502 my $crypted_msg;
503 my $client = $input_socket->accept();
504 my $other_end = getpeername($client);
505 if(not defined $other_end) {
506 daemon_log("client cannot be identified: $!\n");
507 } else {
508 my ($port, $iaddr) = unpack_sockaddr_in($other_end);
509 my $actual_ip = inet_ntoa($iaddr);
510 daemon_log("\naccept client from $actual_ip\n", 5);
511 my $in_msg = &read_from_socket($client);
512 if(defined $in_msg){
513 chomp($in_msg);
514 $crypted_msg = $in_msg;
515 } else {
516 daemon_log("cannot read from $actual_ip\n", 5);
517 }
518 }
519 close($client);
521 # validate acknowledge msg from server
522 $new_server_cipher = &create_ciphering($new_server_passwd);
523 my $msg_hash;
524 eval {
525 my $decrypted_msg = &decrypt_msg($crypted_msg, $new_server_cipher);
526 daemon_log("decrypted register msg: $decrypted_msg", 5);
527 $msg_hash = $xml->XMLin($decrypted_msg, ForceArray=>1);
528 };
529 if($@) {
530 daemon_log("ERROR: do not understand the incoming message:" , 5);
531 daemon_log("$@", 7);
532 } else {
533 my $header = @{$msg_hash->{header}}[0];
534 if($header eq "registered") {
535 $reg_server = $server;
536 last;
537 } elsif($header eq "denied") {
538 my $reason = (&get_content_from_xml_hash($msg_hash, "denied"))[0];
539 daemon_log("registration at $server denied: $reason", 1);
540 } else {
541 daemon_log("cannot register at $server", 1);
542 }
543 }
544 }
545 # if no answer arrive, try next server in list
547 }
549 if(defined $reg_server) {
550 daemon_log("registered at $reg_server", 1);
551 } else {
552 daemon_log("cannot register at any server", 1);
553 daemon_log("exiting!!!", 1);
554 exit(1);
555 }
557 # update the global available variables
558 $server_address = $reg_server;
559 $server_passwd = $new_server_passwd;
560 $server_cipher = $new_server_cipher;
561 return;
562 }
565 #=== FUNCTION ================================================================
566 # NAME: create_xml_hash
567 # PARAMETERS:
568 # RETURNS:
569 # DESCRIPTION:
570 #===============================================================================
571 #sub create_xml_hash {
572 # my ($header, $source, $target, $header_value) = @_;
573 # my $hash = {
574 # header => [$header],
575 # source => [$source],
576 # target => [$target],
577 # $header => [$header_value],
578 # };
579 # daemon_log("create_xml_hash:", 7),
580 # chomp(my $tmp = Dumper $hash);
581 # daemon_log("\t$tmp\n", 7);
582 # return $hash
583 #}
586 #=== FUNCTION ================================================================
587 # NAME: create_xml_string
588 # PARAMETERS:
589 # RETURNS:
590 # DESCRIPTION:
591 #===============================================================================
592 #sub create_xml_string {
593 # my ($xml_hash) = @_ ;
594 # my $xml_string = $xml->XMLout($xml_hash, RootName => 'xml');
595 # $xml_string =~ s/[\n]+//g;
596 # daemon_log("create_xml_string:\n\t$xml_string\n", 7);
597 # return $xml_string;
598 #}
601 #=== FUNCTION ================================================================
602 # NAME: add_content2xml_hash
603 # PARAMETERS:
604 # RETURNS:
605 # DESCRIPTION:
606 #===============================================================================
607 #sub add_content2xml_hash {
608 # my ($xml_ref, $element, $content) = @_;
609 # if(not exists $$xml_ref{$element} ) {
610 # $$xml_ref{$element} = [];
611 # }
612 # my $tmp = $$xml_ref{$element};
613 # push(@$tmp, $content);
614 # return;
615 #}
618 #=== FUNCTION ================================================================
619 # NAME: get_content_from_xml_hash
620 # PARAMETERS: ref : reference to the xml hash
621 # string: key of the value you want
622 # RETURNS: STRING AND ARRAY
623 # DESCRIPTION: if key of the hash is either 'header', 'target' or 'source' the
624 # function returns a string cause it is expected that these keys
625 # do just have one value, all other keys returns an array!!!
626 #===============================================================================
627 #sub get_content_from_xml_hash {
628 # my ($xml_ref, $element) = @_;
629 # my $result = $xml_ref->{$element};
630 # if( $element eq "header" || $element eq "target" || $element eq "source") {
631 # return @$result[0];
632 # }
633 # return @$result;
634 #}
636 # my ($xml_ref, $element) = @_;
637 # if (exists $xml_ref->{$element}) {
638 # my $result = $xml_ref->{$element};
639 # if( $element eq "header" || $element eq "target" || $element eq "source") {
640 # return @$result[0];
641 # } else {
642 # return @$result;
643 # }
644 #
645 # } else {
646 # my $result = ();
647 # return @$result;
648 # }
649 #}
652 #=== FUNCTION ================================================================
653 # NAME: encrypt_msg
654 # PARAMETERS:
655 # RETURNS:
656 # DESCRIPTION:
657 #===============================================================================
658 #sub encrypt_msg {
659 # my ($msg, $my_cipher) = @_;
660 # if(not defined $my_cipher) { print "no cipher object\n"; }
661 # $msg = "\0"x(16-length($msg)%16).$msg;
662 # my $crypted_msg = $my_cipher->encrypt($msg);
663 # chomp($crypted_msg = &encode_base64($crypted_msg));
664 # return $crypted_msg;
665 #}
668 #=== FUNCTION ================================================================
669 # NAME: decrypt_msg
670 # PARAMETERS:
671 # RETURNS:
672 # DESCRIPTION:
673 #===============================================================================
674 #sub decrypt_msg {
675 # my ($crypted_msg, $my_cipher) = @_ ;
676 # $crypted_msg = &decode_base64($crypted_msg);
677 # my $msg = $my_cipher->decrypt($crypted_msg);
678 # $msg =~ s/\0*//g;
679 # return $msg;
680 #}
683 #=== FUNCTION ================================================================
684 # NAME: create_ciphering
685 # PARAMETERS:
686 # RETURNS: cipher object
687 # DESCRIPTION:
688 #===============================================================================
689 #sub create_ciphering {
690 # my ($passwd) = @_;
691 # $passwd = substr(md5_hex("$passwd") x 32, 0, 32);
692 # my $iv = substr(md5_hex('GONICUS GmbH'),0, 16);
693 #
694 # #daemon_log("iv: $iv", 7);
695 # #daemon_log("key: $passwd", 7);
696 # my $my_cipher = Crypt::Rijndael->new($passwd , Crypt::Rijndael::MODE_CBC());
697 # $my_cipher->set_iv($iv);
698 # return $my_cipher;
699 #}
702 #=== FUNCTION ================================================================
703 # NAME: create_passwd
704 # PARAMETERS:
705 # RETURNS: cipher object
706 # DESCRIPTION:
707 #===============================================================================
708 sub create_passwd {
709 my $new_passwd = "";
710 for(my $i=0; $i<31; $i++) {
711 $new_passwd .= ("a".."z","A".."Z",0..9)[int(rand(62))]
712 }
714 return $new_passwd;
715 }
718 #=== FUNCTION ================================================================
719 # NAME: send_msg_hash2address
720 # PARAMETERS: msg string - xml message
721 # PeerAddr string - socket address to send msg
722 # PeerPort string - socket port, if not included in socket address
723 # RETURNS: nothing
724 # DESCRIPTION: ????
725 #===============================================================================
726 #sub send_msg_hash2address {
727 # my ($msg_hash, $address, $passwd) = @_ ;
728 #
729 # # fetch header for logging
730 # my $header = @{$msg_hash->{header}}[0];
731 #
732 # # generiere xml string
733 # my $msg_xml = &create_xml_string($msg_hash);
734 #
735 # # hole das entsprechende passwd aus dem hash
736 # if(not defined $passwd) {
737 # if(exists $known_hosts->{$address}) {
738 # $passwd = $known_hosts->{$address}->{passwd};
739 # } elsif ($address eq $server_address) {
740 # $passwd = $server_passwd;
741 # } else {
742 # daemon_log("$address not known, neither as server nor as client", 1);
743 # return "failed";
744 # }
745 # }
746 #
747 # # erzeuge ein ciphering object
748 # my $act_cipher = &create_ciphering($passwd);
749 #
750 # # encrypt xml msg
751 # my $crypted_msg = &encrypt_msg($msg_xml, $act_cipher);
752 #
753 # # öffne socket
754 # my $socket = &open_socket($address);
755 # if(not defined $socket){
756 # daemon_log("cannot open socket to $address, server not reachable", 1);
757 # daemon_log("cannot send '$header'-msg", 1);
758 # return "failed";
759 # }
760 #
761 # # versende xml msg
762 # print $socket $crypted_msg."\n";
763 #
764 # # schlieĂźe socket
765 # close $socket;
766 #
767 # daemon_log("send '$header'-msg to $address", 5);
768 # daemon_log("crypted_msg:\n\t$crypted_msg", 7);
769 #
770 # return "done";
771 #}
774 #=== FUNCTION ================================================================
775 # NAME: open_socket
776 # PARAMETERS: PeerAddr string something like 192.168.1.1 or 192.168.1.1:10000
777 # [PeerPort] string necessary if port not appended by PeerAddr
778 # RETURNS: socket IO::Socket::INET
779 # DESCRIPTION:
780 #===============================================================================
781 sub open_socket {
782 my ($PeerAddr, $PeerPort) = @_ ;
783 if(defined($PeerPort)){
784 $PeerAddr = $PeerAddr.":".$PeerPort;
785 }
786 my $socket;
787 $socket = new IO::Socket::INET(PeerAddr => $PeerAddr ,
788 Porto => "tcp" ,
789 Type => SOCK_STREAM,
790 Timeout => 5,
791 );
792 if(not defined $socket) {
793 #daemon_log("cannot connect to socket at $PeerAddr, $@\n");
794 return;
795 }
796 daemon_log("open_socket:\n\t$PeerAddr", 7);
797 return $socket;
798 }
801 #=== FUNCTION ================================================================
802 # NAME: read_from_socket
803 # PARAMETERS: socket fh -
804 # RETURNS: result string - readed characters from socket
805 # DESCRIPTION: reads data from socket in 16 byte steps
806 #===============================================================================
807 sub read_from_socket {
808 my ($socket) = @_;
809 my $result = "";
811 $socket->blocking(1);
812 $result = <$socket>;
814 $socket->blocking(0);
815 while ( my $char = <$socket> ) {
816 if (not defined $char) { last }
817 $result .= $char;
818 }
819 return $result;
823 # my ($socket) = @_;
824 # my $result = "";
825 # my $len = 16;
826 # while($len == 16){
827 # my $char;
828 # $len = sysread($socket, $char, 16);
829 # if($len != 16) { last }
830 # if($len != 16) { last }
831 # $result .= $char;
832 # }
833 # return $result;
834 }
837 #=== FUNCTION ================================================================
838 # NAME: print_known_hosts_hash
839 # PARAMETERS:
840 # RETURNS:
841 # DESCRIPTION:
842 #===============================================================================
843 sub print_known_hosts_hash {
844 my ($tmp) = @_;
845 print "####################################\n";
846 print "# status of known_hosts\n";
847 my $hosts;
848 my $host_hash;
849 my @hosts = keys %$known_hosts;
850 foreach my $host (@hosts) {
851 #my @elements = keys %$known_hosts->{$host};
852 my $status = $known_hosts->{$host}->{status} ;
853 my $passwd = $known_hosts->{$host}->{passwd};
854 my $timestamp = $known_hosts->{$host}->{timestamp};
855 print "$host\n";
856 print "\t$status\n";
857 print "\t$passwd\n";
858 print "\t$timestamp\n";
859 }
860 print "####################################\n";
861 return;
862 }
864 #=== FUNCTION ================================================================
865 # NAME:
866 # PARAMETERS:
867 # RETURNS:
868 # DESCRIPTION:
869 #===============================================================================
870 sub create_known_hosts_entry {
871 my ($hostname) = @_;
872 $known_hosts->{$hostname} = {};
873 $known_hosts->{$hostname}->{status} = "none";
874 $known_hosts->{$hostname}->{passwd} = "none";
875 $known_hosts->{$hostname}->{timestamp} = "none";
876 return;
877 }
880 #=== FUNCTION ================================================================
881 # NAME:
882 # PARAMETERS:
883 # RETURNS:
884 # DESCRIPTION:
885 #===============================================================================
886 sub update_known_hosts_entry {
887 my ($hostname, $status, $passwd, $timestamp) = @_;
888 my ($seconds, $minutes, $hours, $monthday, $month,
889 $year, $weekday, $yearday, $sommertime) = localtime(time);
890 $hours = $hours < 10 ? $hours = "0".$hours : $hours;
891 $minutes = $minutes < 10 ? $minutes = "0".$minutes : $minutes;
892 $seconds = $seconds < 10 ? $seconds = "0".$seconds : $seconds;
893 $month+=1;
894 $month = $month < 10 ? $month = "0".$month : $month;
895 $monthday = $monthday < 10 ? $monthday = "0".$monthday : $monthday;
896 $year+=1900;
897 my $t = "$year$month$monthday$hours$minutes$seconds";
899 if($status) {
900 $known_hosts->{$hostname}->{status} = $status;
901 }
902 if($passwd) {
903 $known_hosts->{$hostname}->{passwd} = $passwd;
904 }
905 if($timestamp) {
906 $t = $timestamp;
907 }
908 $known_hosts->{$hostname}->{timestamp} = $t;
909 return;
910 }
913 #=== FUNCTION ================================================================
914 # NAME:
915 # PARAMETERS:
916 # RETURNS:
917 # DESCRIPTION:
918 #===============================================================================
919 sub add_content2known_hosts {
920 my ($hostname, $element, $content) = @_;
921 my ($seconds, $minutes, $hours, $monthday, $month,
922 $year, $weekday, $yearday, $sommertime) = localtime(time);
923 $hours = $hours < 10 ? $hours = "0".$hours : $hours;
924 $minutes = $minutes < 10 ? $minutes = "0".$minutes : $minutes;
925 $seconds = $seconds < 10 ? $seconds = "0".$seconds : $seconds;
926 $month+=1;
927 $month = $month < 10 ? $month = "0".$month : $month;
928 $monthday = $monthday < 10 ? $monthday = "0".$monthday : $monthday;
929 $year+=1900;
930 my $t = "$year$month$monthday$hours$minutes$seconds";
932 $known_hosts->{$hostname}->{$element} = $content;
933 $known_hosts->{$hostname}->{timestamp} = $t;
934 return;
935 }
938 #=== FUNCTION ================================================================
939 # NAME:
940 # PARAMETERS:
941 # RETURNS:
942 # DESCRIPTION:
943 #===============================================================================
944 sub process_incoming_msg {
945 my ($crypted_msg) = @_;
946 if(not defined $crypted_msg) {
947 daemon_log("function 'process_incoming_msg': got no msg", 7);
948 }
949 $crypted_msg =~ /^([\s\S]*?)\.(\d{1,3}?)\.(\d{1,3}?)\.(\d{1,3}?)\.(\d{1,3}?)$/;
950 $crypted_msg = $1;
951 my $host = sprintf("%s.%s.%s.%s", $2, $3, $4, $5);
952 daemon_log("msg from host:", 1);
953 daemon_log("\t$host", 1);
954 daemon_log("crypted msg:", 7);
955 daemon_log("\t$crypted_msg", 7);
957 my $act_cipher = &create_ciphering($server_passwd);
959 # try to decrypt incoming msg
960 my ($msg, $msg_hash);
961 eval{
962 $msg = &decrypt_msg($crypted_msg, $act_cipher);
963 $msg_hash = $xml->XMLin($msg, ForceArray=>1);
964 };
965 if($@) {
966 daemon_log("ERROR: incoming msg cannot be decrypted with server passwd", 1);
967 return;
968 }
970 my $header = @{$msg_hash->{header}}[0];
972 daemon_log("receive '$header' from $host", 1);
974 #check whether msg to process is a event
975 opendir(DIR, $event_dir)
976 or daemon_log("cannot find directory $event_dir, no events specified", 5);
977 my $file_name;
978 while(defined($file_name = readdir(DIR))){
979 if ($file_name eq "." || $file_name eq "..") {
980 next;
981 }
982 if ($file_name eq $header) {
983 my $cmd = "$event_dir/$file_name '$msg'";
984 my $result_xml = "";
985 open(PIPE, "$cmd 2>&1 |");
986 while(<PIPE>) {
987 $result_xml.=$_;
988 last;
989 }
990 close(PIPE);
991 my $res_hash = &transform_msg2hash($result_xml);
992 my $res_target = @{$res_hash->{target}}[0];
993 &send_msg_hash2address($res_hash, $server_address);
995 return;
996 }
997 }
998 close(DIR);
999 daemon_log("could not assign the msg $header to an event", 5);
1001 if ($header eq 'new_ldap_config') { if ($ldap_enabled == 1) {&new_ldap_config($msg_hash)}}
1002 elsif ($header eq 'ping') { &got_ping($msg_hash) }
1003 elsif ($header eq 'wake_up') { &execute_event($msg_hash)}
1004 elsif ($header eq 'new_passwd') { &new_passwd()}
1005 else { daemon_log("ERROR: no function assigned to msg $header", 5) }
1007 return;
1008 }
1011 #=== FUNCTION ================================================================
1012 # NAME:
1013 # PARAMETERS:
1014 # RETURNS:
1015 # DESCRIPTION:
1016 #===============================================================================
1017 sub update_status {
1018 my ($new_status) = @_ ;
1019 my $out_hash = &create_xml_hash("update_status", $client_address, $server_address);
1020 &add_content2xml_hash($out_hash, "update_status", $new_status);
1021 &send_msg_hash2address($out_hash, $server_address);
1022 return;
1023 }
1026 #=== FUNCTION ================================================================
1027 # NAME:
1028 # PARAMETERS:
1029 # RETURNS:
1030 # DESCRIPTION:
1031 #===============================================================================
1032 sub server_leaving {
1033 my ($msg_hash) = @_ ;
1034 my $source = &get_content_from_xml_hash("source");
1035 my $header = &get_content_from_xml_hash("header");
1037 daemon_log("gosa daemon $source is going down, cause registration procedure", 1);
1038 my $server_address = "none";
1039 my $server_passwd = "none";
1040 my $server_cipher = "none";
1042 # reinitialization of default values in config file
1043 &read_configfile;
1045 # registrated at new daemon
1046 ®ister_at_server();
1048 return;
1049 }
1052 sub got_ping {
1053 my ($msg_hash) = @_ ;
1055 my $source = &get_content_from_xml_hash($msg_hash, 'source');
1056 my $target = &get_content_from_xml_hash($msg_hash, 'target');
1057 my $header = &get_content_from_xml_hash($msg_hash, 'header');
1059 &add_content2known_hosts(hostname=>$target, status=>$header);
1061 my $out_hash = &create_xml_hash("got_ping", $target, $source);
1062 &send_msg_hash2address($out_hash, $source, $server_passwd);
1064 return;
1065 }
1068 sub new_ldap_config {
1069 my ($msg_hash) = @_ ;
1070 my $element;
1071 my @ldap_uris;
1072 my $ldap_base;
1073 my @ldap_options;
1074 my @pam_options;
1075 my @nss_options;
1076 my $goto_admin;
1077 my $goto_secret;
1078 my $admin_base= "";
1079 my $department= "";
1080 my $unit_tag;
1082 # Transform input into array
1083 while ( my ($key, $value) = each(%$msg_hash) ) {
1084 if ($key =~ /^(source|target|header)$/) {
1085 next;
1086 }
1088 foreach $element (@$value) {
1089 if ($key =~ /^ldap_uri$/) {
1090 push (@ldap_uris, $element);
1091 next;
1092 }
1093 if ($key =~ /^ldap_base$/) {
1094 $ldap_base= $element;
1095 next;
1096 }
1097 if ($key =~ /^goto_admin$/) {
1098 $goto_admin= $element;
1099 next;
1100 }
1101 if ($key =~ /^goto_secret$/) {
1102 $goto_secret= $element;
1103 next;
1104 }
1105 if ($key =~ /^ldap_cfg$/) {
1106 push (@ldap_options, "$element");
1107 next;
1108 }
1109 if ($key =~ /^pam_cfg$/) {
1110 push (@pam_options, "$element");
1111 next;
1112 }
1113 if ($key =~ /^nss_cfg$/) {
1114 push (@nss_options, "$element");
1115 next;
1116 }
1117 if ($key =~ /^admin_base$/) {
1118 $admin_base= $element;
1119 next;
1120 }
1121 if ($key =~ /^department$/) {
1122 $department= $element;
1123 next;
1124 }
1125 if ($key =~ /^unit_tag$/) {
1126 $unit_tag= $element;
1127 next;
1128 }
1129 }
1130 }
1132 # Unit tagging enabled?
1133 if (defined $unit_tag){
1134 push (@pam_options, "pam_filter gosaUnitTag=$unit_tag");
1135 push (@nss_options, "nss_base_passwd $admin_base?sub?gosaUnitTag=$unit_tag");
1136 push (@nss_options, "nss_base_group $admin_base?sub?gosaUnitTag=$unit_tag");
1137 }
1139 # Setup ldap.conf
1140 my $file1;
1141 my $file2;
1142 open(file1, "> $ldap_config");
1143 print file1 "# This file was automatically generated by gosa-si-client. Do not change.\n";
1144 print file1 "URI";
1145 foreach $element (@ldap_uris) {
1146 print file1 " $element";
1147 }
1148 print file1 "\nBASE $ldap_base\n";
1149 foreach $element (@ldap_options) {
1150 print file1 "$element\n";
1151 }
1152 close (file1);
1153 daemon_log("wrote $ldap_config", 5);
1155 # Setup pam_ldap.conf / libnss_ldap.conf
1156 open(file1, "> $pam_config");
1157 open(file2, "> $nss_config");
1158 print file1 "# This file was automatically generated by gosa-si-client. Do not change.\n";
1159 print file2 "# This file was automatically generated by gosa-si-client. Do not change.\n";
1160 print file1 "uri";
1161 print file2 "uri";
1162 foreach $element (@ldap_uris) {
1163 print file1 " $element";
1164 print file2 " $element";
1165 }
1166 print file1 "\nbase $ldap_base\n";
1167 print file2 "\nbase $ldap_base\n";
1168 foreach $element (@pam_options) {
1169 print file1 "$element\n";
1170 }
1171 foreach $element (@nss_options) {
1172 print file2 "$element\n";
1173 }
1174 close (file2);
1175 daemon_log("wrote $nss_config", 5);
1176 close (file1);
1177 daemon_log("wrote $pam_config", 5);
1179 # Create goto.secrets if told so - for compatibility reasons
1180 if (defined $goto_admin){
1181 open(file1, "> /etc/goto/secret");
1182 close(file1);
1183 chown(0,0, "/etc/goto/secret");
1184 chmod(0600, "/etc/goto/secret");
1185 open(file1, "> /etc/goto/secret");
1186 print file1 "GOTOADMIN=\"$goto_admin\"\nGOTOSECRET=\"$goto_secret\"\n";
1187 close(file1);
1188 daemon_log("wrote /etc/goto/secret", 5);
1189 }
1193 # Write shell based config
1194 my $cfg_name= dirname($ldap_config)."/ldap-shell.conf";
1195 open(file1, "> $cfg_name");
1196 print file1 "LDAP_BASE=\"$ldap_base\"\n";
1197 print file1 "ADMIN_BASE=\"$admin_base\"\n";
1198 print file1 "DEPARTMENT=\"$department\"\n";
1199 print file1 "UNIT_TAG=\"".(defined $unit_tag ? "$unit_tag" : "")."\"\n";
1200 print file1 "UNIT_TAG_FILTER=\"".(defined $unit_tag ? "(gosaUnitTag=$unit_tag)" : "")."\"\n";
1201 close(file1);
1202 daemon_log("wrote $cfg_name", 5);
1204 return;
1206 }
1209 sub execute_event {
1210 my ($msg_hash)= @_;
1211 my $configdir= '/etc/gosa-si/client/events/';
1212 my $result;
1214 my $header = &get_content_from_xml_hash($msg_hash, 'header');
1215 my $source = &get_content_from_xml_hash($msg_hash, 'source');
1216 my $target = &get_content_from_xml_hash($msg_hash, 'target');
1219 if((not defined $source)
1220 && (not defined $target)
1221 && (not defined $header)) {
1222 daemon_log("ERROR: Entries missing in XML msg for gosa events under $configdir");
1223 } else {
1224 my $parameters="";
1225 my @params = &get_content_from_xml_hash($msg_hash, $header);
1226 my $params = join(", ", @params);
1227 daemon_log("execute_event: got parameters: $params", 5);
1229 if (@params) {
1230 foreach my $param (@params) {
1231 my $param_value = (&get_content_from_xml_hash($msg_hash, $param))[0];
1232 daemon_log("execute_event: parameter -> value: $param -> $param_value", 7);
1233 $parameters.= " ".$param_value;
1234 }
1235 }
1237 my $cmd= $configdir.$header."$parameters";
1238 daemon_log("execute_event: executing cmd: $cmd", 7);
1239 $result= "";
1240 open(PIPE, "$cmd 2>&1 |");
1241 while(<PIPE>) {
1242 $result.=$_;
1243 }
1244 close(PIPE);
1245 }
1247 # process the event result
1250 return;
1251 }
1254 sub new_passwd {
1255 # my ($msg_hash) = @_ ;
1256 my $new_server_passwd = &create_passwd();
1257 my $new_server_cipher = &create_ciphering($new_server_passwd);
1259 my $out_hash = &create_xml_hash("new_passwd", $client_address, $server_address, $new_server_passwd);
1261 &send_msg_hash2address($out_hash, $server_address, $server_passwd);
1263 $server_passwd = $new_server_passwd;
1264 $server_cipher = $new_server_cipher;
1265 return;
1266 }
1271 #==== MAIN = main ==============================================================
1273 # parse commandline options
1274 Getopt::Long::Configure( "bundling" );
1275 GetOptions("h|help" => \&usage,
1276 "c|config=s" => \$cfg_file,
1277 "f|foreground" => \$foreground,
1278 "v|verbose+" => \$verbose,
1279 );
1281 # read and set config parameters
1282 &check_cmdline_param ;
1283 &read_configfile;
1284 &check_pid;
1286 if ( ! $foreground ) {
1287 open STDIN, '/dev/null' or die "Can’t read /dev/null: $!";
1288 open STDOUT, '>>/dev/null' or die "Can't write to /dev/null: $!";
1289 open STDERR, '>>/dev/null' or die "Can't write to /dev/null: $!";
1290 }
1293 # restart daemon log file
1294 if(-e $log_file ) { unlink $log_file }
1295 daemon_log(" ", 1);
1296 daemon_log("$0 started!", 1);
1298 # Just fork, if we"re not in foreground mode
1299 if( ! $foreground ) { $pid = fork(); }
1300 else { $pid = $$; }
1302 # Do something useful - put our PID into the pid_file
1303 if( 0 != $pid ) {
1304 open( LOCK_FILE, ">$pid_file" );
1305 print LOCK_FILE "$pid\n";
1306 close( LOCK_FILE );
1307 if( !$foreground ) { exit( 0 ) };
1308 }
1310 # detect own ip and mac address
1311 $network_interface= &get_interface_for_ip($client_ip);
1312 $client_mac_address= &get_mac($network_interface);
1314 # ($client_ip, $client_mac_address) = &get_ip_and_mac();
1315 #if (not defined $client_ip) {
1316 # die "EXIT: ip address of $0 could not be detected";
1317 #}
1318 daemon_log("client ip address detected: $client_ip", 1);
1319 daemon_log("client mac address detected: $client_mac_address", 1);
1321 # prepare variables
1322 if (defined $server_ip && defined $server_port) {
1323 $server_address = $server_ip.":".$server_port;
1324 }
1326 # this is necessary that gosa-si-server knowns to which ip-address he can send msgs
1327 if( $client_ip eq "0.0.0.0" ) {
1328 $client_ip = "127.0.0.1";
1329 }
1330 $client_address = $client_ip.":".$client_port;
1332 # setup xml parser
1333 $xml = new XML::Simple();
1335 # create input socket
1336 daemon_log(" ", 1);
1337 $rbits = $wbits = $ebits = "";
1338 $input_socket = IO::Socket::INET->new(LocalPort => $client_port,
1339 Type => SOCK_STREAM,
1340 Reuse => 1,
1341 Listen => 20,
1342 );
1343 if(not defined $input_socket){
1344 daemon_log("cannot be a tcp server at $client_port : $@\n");
1345 } else {
1346 daemon_log("start client at $client_address",1) ;
1347 vec($rbits, fileno $input_socket, 1) = 1;
1348 vec($wbits, fileno $input_socket, 1) = 1;
1349 }
1351 # register at server
1352 daemon_log(" ", 1);
1353 ®ister_at_server();
1356 ##############
1357 # Debugging
1358 #############
1359 #sleep(2);
1360 #&update_status("ich_bin_ein_neuer_status");
1362 ###################################
1363 #everything ready, okay, lets start
1364 ###################################
1365 while(1) {
1366 my ($rout, $wout);
1367 my $nf = select($rout=$rbits, $wout=$wbits, undef, undef);
1369 # error handling
1370 if($nf < 0 ) {
1371 }
1373 # something is coming in
1374 if(vec $rout, fileno $input_socket, 1) {
1375 my $client = $input_socket->accept();
1376 my $other_end = getpeername($client);
1378 if(not defined $other_end) {
1379 daemon_log("client cannot be identified: $!");
1380 } else {
1381 my ($port, $iaddr) = unpack_sockaddr_in($other_end);
1382 my $actual_ip = inet_ntoa($iaddr);
1383 daemon_log("accept client from $actual_ip", 5);
1384 my $in_msg = &read_from_socket($client);
1385 if(defined $in_msg){
1386 chomp($in_msg);
1387 $in_msg = $in_msg.".".$actual_ip;
1388 &process_incoming_msg($in_msg);
1390 }
1391 }
1392 }
1393 }