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