Code

Increased waiting time.
[gosa.git] / gosa-si / gosa-si-client
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 POE qw(Component::Server::TCP);
29 use IO::Socket::INET;
30 use NetAddr::IP;
31 use Data::Dumper;
32 use Crypt::Rijndael;
33 use GOSA::GosaSupportDaemon;
34 use Digest::MD5  qw(md5_hex md5 md5_base64);
35 use MIME::Base64;
36 use XML::Simple;
37 use Net::DNS;
39 my $event_dir = "/usr/lib/gosa-si/client/events";
40 use lib "/usr/lib/gosa-si/client/events";
42 my ($cfg_file, %cfg_defaults, $foreground, $verbose, $pid_file, $procid, $pid, $log_file);
43 my ($server_ip, $server_port, $server_key, $server_timeout, $server_domain, $server_key_lifetime);
44 my ($client_ip, $client_port, $client_mac_address, $ldap_enabled, $ldap_config, $pam_config, $nss_config);
45 my $xml;
46 my $default_server_key;
47 my $event_hash;
48 my @servers;
49 my $gotoHardwareChecksum;
51 # globalise variables which are used in imported events
52 our $cfg_file;
53 our $server_address;
54 our $client_address;
55 our $server_key;
57 # default variables
58 our $REGISTERED_FLAG = 1;
60 %cfg_defaults = (
61 "general" =>
62     {"log_file" => [\$log_file, "/var/run/".$0.".log"],
63     "pid_file" => [\$pid_file, "/var/run/".$0.".pid"],
64     },
65 "client" => 
66     {"client_port" => [\$client_port, "20083"],
67      "client_ip" => [\$client_ip, "0.0.0.0"],
68      "client_mac_address" => [\$client_mac_address, "00:00:00:00:00:00"],
69      "ldap" => [\$ldap_enabled, 1],
70      "ldap_config" => [\$ldap_config, "/etc/ldap/ldap.conf"],
71      "pam_config" => [\$pam_config, "/etc/pam_ldap.conf"],
72      "nss_config" => [\$nss_config, "/etc/libnss_ldap.conf"],
73     },
74 "server" =>
75     {"server_ip" => [\$server_ip, "127.0.0.1"],
76     "server_port" => [\$server_port, "20081"],
77     "server_key" => [\$server_key, ""],
78     "server_timeout" => [\$server_timeout, 10],
79     "server_domain" => [\$server_domain, ""],
80     "server_key_lifetime" => [\$server_key_lifetime, 600], 
81     },
83 );
86 #=== FUNCTIONS = functions =====================================================
88 #===  FUNCTION  ================================================================
89 #         NAME: check_cmdline_param
90 #   PARAMETERS: 
91 #      RETURNS:  
92 #  DESCRIPTION: 
93 #===============================================================================
94 sub check_cmdline_param () {
95     my $err_config;
96     my $err_counter = 0;
97         if(not defined($cfg_file)) {
98                 $cfg_file = "/etc/gosa-si/client.conf";
99                 if(! -r $cfg_file) {
100                         $err_config = "please specify a config file";
101                         $err_counter += 1;
102                 }
103     }
104     if( $err_counter > 0 ) {
105         &usage( "", 1 );
106         if( defined( $err_config)) { print STDERR "$err_config\n"}
107         print STDERR "\n";
108         exit( -1 );
109     }
113 #===  FUNCTION  ================================================================
114 #         NAME:  read_configfile
115 #   PARAMETERS:  cfg_file - string - 
116 #      RETURNS:  
117 #  DESCRIPTION: 
118 #===============================================================================
119 sub read_configfile {
120     my ($cfg_file, %cfg_defaults) = @_ ;
121     my $cfg;
122     if( defined( $cfg_file) && ( length($cfg_file) > 0 )) {
123         if( -r $cfg_file ) {
124             $cfg = Config::IniFiles->new( -file => $cfg_file );
125         } else {
126             print STDERR "Couldn't read config file!";
127         }
128     } else {
129         $cfg = Config::IniFiles->new() ;
130     }
131     foreach my $section (keys %cfg_defaults) {
132         foreach my $param (keys %{$cfg_defaults{ $section }}) {
133             my $pinfo = $cfg_defaults{ $section }{ $param };
134             ${@$pinfo[ 0 ]} = $cfg->val( $section, $param, @$pinfo[ 1 ] );
135         }
136     }
140 #===  FUNCTION  ================================================================
141 #         NAME: check_pid
142 #   PARAMETERS:
143 #      RETURNS:
144 #  DESCRIPTION:
145 #===============================================================================
146 sub check_pid {
147     $pid = -1;
148     # Check, if we are already running
149     if( open(LOCK_FILE, "<$pid_file") ) {
150         $pid = <LOCK_FILE>;
151         if( defined $pid ) {
152             chomp( $pid );
153             if( -f "/proc/$pid/stat" ) {
154                 my($stat) = `cat /proc/$pid/stat` =~ m/$pid \((.+)\).*/;
155                 if( $0 eq $stat ) {
156                     close( LOCK_FILE );
157                     exit -1;
158                 }
159             }
160         }
161         close( LOCK_FILE );
162         unlink( $pid_file );
163     }
165     # create a syslog msg if it is not to possible to open PID file
166     if (not sysopen(LOCK_FILE, $pid_file, O_WRONLY|O_CREAT|O_EXCL, 0644)) {
167         my($msg) = "Couldn't obtain lockfile '$pid_file' ";
168         if (open(LOCK_FILE, '<', $pid_file)
169                 && ($pid = <LOCK_FILE>))
170         {
171             chomp($pid);
172             $msg .= "(PID $pid)\n";
173         } else {
174             $msg .= "(unable to read PID)\n";
175         }
176         if( ! ($foreground) ) {
177             openlog( $0, "cons,pid", "daemon" );
178             syslog( "warning", $msg );
179             closelog();
180         }
181         else {
182             print( STDERR " $msg " );
183         }
184         exit( -1 );
185     }
189 #===  FUNCTION  ================================================================
190 #         NAME:  logging
191 #   PARAMETERS:  level - string - default 'info' 
192 #                msg - string - 
193 #                facility - string - default 'LOG_DAEMON' 
194 #      RETURNS:  
195 #  DESCRIPTION: 
196 #===============================================================================
197 sub daemon_log {
198     # log into log_file
199     my( $msg, $level ) = @_;
200     if(not defined $msg) { return }
201     if(not defined $level) { $level = 1 }
202     if(defined $log_file){
203         open(LOG_HANDLE, ">>$log_file");
204         if(not defined open( LOG_HANDLE, ">>$log_file" )) {
205             print STDERR "cannot open $log_file: $!";
206             return }
207             chomp($msg);
208             if($level <= $verbose){
209                 my ($seconds, $minutes, $hours, $monthday, $month,
210                         $year, $weekday, $yearday, $sommertime) = localtime(time);
211                 $hours = $hours < 10 ? $hours = "0".$hours : $hours;
212                 $minutes = $minutes < 10 ? $minutes = "0".$minutes : $minutes;
213                 $seconds = $seconds < 10 ? $seconds = "0".$seconds : $seconds;
214                 my @monthnames = ("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec");
215                 $month = $monthnames[$month];
216                 $monthday = $monthday < 10 ? $monthday = "0".$monthday : $monthday;
217                 $year+=1900;
218                 my $name = $0;
219                 $name =~ s/\.\///;
221                 my $log_msg = "$month $monthday $hours:$minutes:$seconds $name $msg\n";
222                 print LOG_HANDLE $log_msg;
223                 if( $foreground ) { 
224                     print STDERR $log_msg;
225                 }
226             }
227         close( LOG_HANDLE );
228     }
229 #log into syslog
230 #    my ($msg, $level, $facility) = @_;
231 #    if(not defined $msg) {return}
232 #    if(not defined $level) {$level = "info"}
233 #    if(not defined $facility) {$facility = "LOG_DAEMON"}
234 #    openlog($0, "pid,cons,", $facility);
235 #    syslog($level, $msg);
236 #    closelog;
237 #    return;
241 #===  FUNCTION  ================================================================
242 #         NAME:  get_interfaces 
243 #   PARAMETERS:  none
244 #      RETURNS:  (list of interfaces) 
245 #  DESCRIPTION:  Uses proc fs (/proc/net/dev) to get list of interfaces.
246 #===============================================================================
247 sub get_interfaces {
248     my @result;
249     my $PROC_NET_DEV= ('/proc/net/dev');
251     open(PROC_NET_DEV, "<$PROC_NET_DEV")
252         or die "Could not open $PROC_NET_DEV";
254     my @ifs = <PROC_NET_DEV>;
256     close(PROC_NET_DEV);
258     # Eat first two line
259     shift @ifs;
260     shift @ifs;
262     chomp @ifs;
263     foreach my $line(@ifs) {
264         my $if= (split /:/, $line)[0];
265         $if =~ s/^\s+//;
266         push @result, $if;
267     }
269     return @result;
272 #===  FUNCTION  ================================================================
273 #         NAME:  get_mac 
274 #   PARAMETERS:  interface name (i.e. eth0)
275 #      RETURNS:  (mac address) 
276 #  DESCRIPTION:  Uses ioctl to get mac address directly from system.
277 #===============================================================================
278 sub get_mac {
279         my $ifreq= shift;
280         my $result;
281         if ($ifreq && length($ifreq) > 0) { 
282                 if($ifreq eq "all") {
283                         if(defined($server_ip)) {
284                                 $result = &get_local_mac_for_remote_ip($server_ip);
285                         } 
286                         elsif ($client_mac_address && length($client_mac_address) > 0 && !($client_mac_address eq "00:00:00:00:00:00")){
287                                 $result = &client_mac_address;
288                         } 
289                         else {
290                                 $result = "00:00:00:00:00:00";
291                         }
292                 } else {
293                         my $SIOCGIFHWADDR= 0x8927;     # man 2 ioctl_list
295                         # A configured MAC Address should always override a guessed value
296                         if ($client_mac_address and length($client_mac_address) > 0 and not($client_mac_address eq "00:00:00:00:00:00")) {
297                                 $result= $client_mac_address;
298                         }
299                         else {
300                                 socket SOCKET, PF_INET, SOCK_DGRAM, getprotobyname('ip')
301                                         or die "socket: $!";
303                                 if(ioctl SOCKET, $SIOCGIFHWADDR, $ifreq) {
304                                         my ($if, $mac)= unpack 'h36 H12', $ifreq;
306                                         if (length($mac) > 0) {
307                                                 $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])$/;
308                                                 $mac= sprintf("%s:%s:%s:%s:%s:%s", $1, $2, $3, $4, $5, $6);
309                                                 $result = $mac;
310                                         }
311                                 }
312                         }
313                 }
314         }
315         return $result;
319 #===  FUNCTION  ================================================================
320 #         NAME:  get_interface_for_ip
321 #   PARAMETERS:  ip address (i.e. 192.168.0.1)
322 #      RETURNS:  array: list of interfaces if ip=0.0.0.0, matching interface if found, undef else
323 #  DESCRIPTION:  Uses proc fs (/proc/net/dev) to get list of interfaces.
324 #===============================================================================
325 sub get_interface_for_ip {
326     my $result;
327     my $ip= shift;
328     if ($ip && length($ip) > 0) {
329         my @ifs= &get_interfaces();
330         if($ip eq "0.0.0.0") {
331             $result = "all";
332         } else {
333             foreach (@ifs) {
334                 my $if=$_;
335                 if(get_ip($if) eq $ip) {
336                     $result = $if;
337                     last;
338                 }
339             }       
340         }
341     }       
342     return $result;
346 #===  FUNCTION  ================================================================
347 #         NAME:  get_ip 
348 #   PARAMETERS:  interface name (i.e. eth0)
349 #      RETURNS:  (ip address) 
350 #  DESCRIPTION:  Uses ioctl to get ip address directly from system.
351 #===============================================================================
352 sub get_ip {
353     my $ifreq= shift;
354     my $result= "";
355     my $SIOCGIFADDR= 0x8915;       # man 2 ioctl_list
356         my $proto= getprotobyname('ip');
358     socket SOCKET, PF_INET, SOCK_DGRAM, $proto
359         or die "socket: $!";
361     if(ioctl SOCKET, $SIOCGIFADDR, $ifreq) {
362         my ($if, $sin)    = unpack 'a16 a16', $ifreq;
363         my ($port, $addr) = sockaddr_in $sin;
364         my $ip            = inet_ntoa $addr;
366         if ($ip && length($ip) > 0) {
367             $result = $ip;
368         }
369     }
371     return $result;
375 #===  FUNCTION  ================================================================
376 #         NAME:  get_local_mac_for_remote_ip
377 #   PARAMETERS:  none (takes server_ip from global variable)
378 #      RETURNS:  (ip address from interface that is used for communication) 
379 #  DESCRIPTION:  Uses ioctl to get routing table from system, checks which entry
380 #                matches (defaultroute last).
381 #===============================================================================
382 sub get_local_mac_for_remote_ip {
383         my $server_ip= shift;
384         my $result= "00:00:00:00:00:00";
386         if($server_ip =~ /^(\d\d?\d?\.){3}\d\d?\d?$/) {
387                 my $PROC_NET_ROUTE= ('/proc/net/route');
389                 open(PROC_NET_ROUTE, "<$PROC_NET_ROUTE")
390                         or die "Could not open $PROC_NET_ROUTE";
392                 my @ifs = <PROC_NET_ROUTE>;
394                 close(PROC_NET_ROUTE);
396                 # Eat header line
397                 shift @ifs;
398                 chomp @ifs;
399                 foreach my $line(@ifs) {
400                         my ($Iface,$Destination,$Gateway,$Flags,$RefCnt,$Use,$Metric,$Mask,$MTU,$Window,$IRTT)=split(/\s/, $line);
401                         my $destination;
402                         my $mask;
403                         my ($d,$c,$b,$a)=unpack('a2 a2 a2 a2', $Destination);
404                         $destination= sprintf("%d.%d.%d.%d", hex($a), hex($b), hex($c), hex($d));
405                         ($d,$c,$b,$a)=unpack('a2 a2 a2 a2', $Mask);
406                         $mask= sprintf("%d.%d.%d.%d", hex($a), hex($b), hex($c), hex($d));
407                         if(new NetAddr::IP($server_ip)->within(new NetAddr::IP($destination, $mask))) {
408                                 # destination matches route, save mac and exit
409                                 $result= &get_mac($Iface);
410                                 last;
411                         }
412                 }
413         } else {
414                 daemon_log("get_local_mac_for_remote_ip was called with a non-ip parameter: $server_ip", 1);
415         }
416         return $result;
419 sub get_local_ip_for_remote_ip {
420         my $server_ip= shift;
421         my $result="0.0.0.0";
423         if($server_ip =~ /^(\d\d?\d?\.){3}\d\d?\d?$/) {
424                 if($server_ip eq "127.0.0.1") {
425                         $result="127.0.0.1";
426                 } else {
427                         my $PROC_NET_ROUTE= ('/proc/net/route');
429                         open(PROC_NET_ROUTE, "<$PROC_NET_ROUTE")
430                                 or die "Could not open $PROC_NET_ROUTE";
432                         my @ifs = <PROC_NET_ROUTE>;
434                         close(PROC_NET_ROUTE);
436                         # Eat header line
437                         shift @ifs;
438                         chomp @ifs;
439                         foreach my $line(@ifs) {
440                                 my ($Iface,$Destination,$Gateway,$Flags,$RefCnt,$Use,$Metric,$Mask,$MTU,$Window,$IRTT)=split(/\s/, $line);
441                                 my $destination;
442                                 my $mask;
443                                 my ($d,$c,$b,$a)=unpack('a2 a2 a2 a2', $Destination);
444                                 $destination= sprintf("%d.%d.%d.%d", hex($a), hex($b), hex($c), hex($d));
445                                 ($d,$c,$b,$a)=unpack('a2 a2 a2 a2', $Mask);
446                                 $mask= sprintf("%d.%d.%d.%d", hex($a), hex($b), hex($c), hex($d));
447                                 if(new NetAddr::IP($server_ip)->within(new NetAddr::IP($destination, $mask))) {
448                                         # destination matches route, save mac and exit
449                                         $result= &get_ip($Iface);
450                                         last;
451                                 }
452                         }
453                 }
454         } else {
455                 daemon_log("get_local_ip_for_remote_ip was called with a non-ip parameter: $server_ip", 1);
456         }
457         return $result;
460 sub new_ldap_config {
461     my ($msg_hash) = @_ ;
462     my $element;
463     my @ldap_uris;
464     my $ldap_base;
465     my @ldap_options;
466     my @pam_options;
467     my @nss_options;
468     my $goto_admin;
469     my $goto_secret;
470     my $admin_base= "";
471     my $department= "";
472     my $unit_tag;
474     # Transform input into array
475     while ( my ($key, $value) = each(%$msg_hash) ) {
476         if ($key =~ /^(source|target|header)$/) {
477                 next;
478         }
480         foreach $element (@$value) {
481                 if ($key =~ /^ldap_uri$/) {
482                         push (@ldap_uris, $element);
483                         next;
484                 }
485                 if ($key =~ /^ldap_base$/) {
486                         $ldap_base= $element;
487                         next;
488                 }
489                 if ($key =~ /^goto_admin$/) {
490                         $goto_admin= $element;
491                         next;
492                 }
493                 if ($key =~ /^goto_secret$/) {
494                         $goto_secret= $element;
495                         next;
496                 }
497                 if ($key =~ /^ldap_cfg$/) {
498                         push (@ldap_options, "$element");
499                         next;
500                 }
501                 if ($key =~ /^pam_cfg$/) {
502                         push (@pam_options, "$element");
503                         next;
504                 }
505                 if ($key =~ /^nss_cfg$/) {
506                         push (@nss_options, "$element");
507                         next;
508                 }
509                 if ($key =~ /^admin_base$/) {
510                         $admin_base= $element;
511                         next;
512                 }
513                 if ($key =~ /^department$/) {
514                         $department= $element;
515                         next;
516                 }
517                 if ($key =~ /^unit_tag$/) {
518                         $unit_tag= $element;
519                         next;
520                 }
521         }
522     }
524     # Unit tagging enabled?
525     if (defined $unit_tag){
526             push (@pam_options, "pam_filter gosaUnitTag=$unit_tag");
527             push (@nss_options, "nss_base_passwd  $admin_base?sub?gosaUnitTag=$unit_tag");
528             push (@nss_options, "nss_base_group   $admin_base?sub?gosaUnitTag=$unit_tag");
529     }
531     # Setup ldap.conf
532     my $file1;
533     my $file2;
534     open(file1, "> $ldap_config");
535     print file1 "# This file was automatically generated by gosa-si-client. Do not change.\n";
536     print file1 "URI";
537     foreach $element (@ldap_uris) {
538         print file1 " $element";
539     }
540     print file1 "\nBASE $ldap_base\n";
541     foreach $element (@ldap_options) {
542         print file1 "$element\n";
543     }
544     close (file1);
545     daemon_log("wrote $ldap_config", 5);
547     # Setup pam_ldap.conf / libnss_ldap.conf
548     open(file1, "> $pam_config");
549     open(file2, "> $nss_config");
550     print file1 "# This file was automatically generated by gosa-si-client. Do not change.\n";
551     print file2 "# This file was automatically generated by gosa-si-client. Do not change.\n";
552     print file1 "uri";
553     print file2 "uri";
554     foreach $element (@ldap_uris) {
555         print file1 " $element";
556         print file2 " $element";
557     }
558     print file1 "\nbase $ldap_base\n";
559     print file2 "\nbase $ldap_base\n";
560     foreach $element (@pam_options) {
561         print file1 "$element\n";
562     }
563     foreach $element (@nss_options) {
564         print file2 "$element\n";
565     }
566     close (file2);
567     daemon_log("wrote $nss_config", 5);
568     close (file1);
569     daemon_log("wrote $pam_config", 5);
571     # Create goto.secrets if told so - for compatibility reasons
572     if (defined $goto_admin){
573             open(file1, "> /etc/goto/secret");
574             close(file1);
575             chown(0,0, "/etc/goto/secret");
576             chmod(0600, "/etc/goto/secret");
577             open(file1, "> /etc/goto/secret");
578             print file1 "GOTOADMIN=\"$goto_admin\"\nGOTOSECRET=\"$goto_secret\"\n";
579             close(file1);
580             daemon_log("wrote /etc/goto/secret", 5);
581     }
583     
585     # Write shell based config
586     my $cfg_name= dirname($ldap_config)."/ldap-shell.conf";
587     open(file1, "> $cfg_name");
588     print file1 "LDAP_BASE=\"$ldap_base\"\n";
589     print file1 "ADMIN_BASE=\"$admin_base\"\n";
590     print file1 "DEPARTMENT=\"$department\"\n";
591     print file1 "UNIT_TAG=\"".(defined $unit_tag ? "$unit_tag" : "")."\"\n";
592     print file1 "UNIT_TAG_FILTER=\"".(defined $unit_tag ? "(gosaUnitTag=$unit_tag)" : "")."\"\n";
593     close(file1);
594     daemon_log("wrote $cfg_name", 5);
596     return;
601 sub generate_hw_digest {
602         my $hw_data;
603         foreach my $line (split /\n/, `cat /proc/bus/pci/devices`) {
604                 $hw_data.= sprintf "%s", $line =~ /[^\s]+\s([^\s]+)\s.*/;
605         }
606         return(md5_base64($hw_data));
610 sub create_passwd {
611     my $new_passwd = "";
612     for(my $i=0; $i<31; $i++) {
613         $new_passwd .= ("a".."z","A".."Z",0..9)[int(rand(62))]
614     }
616     return $new_passwd;
620 sub get_server_addresses {
621     my $domain= shift;
622     my @result;
623  
624     my $error = 0;
625     my $res   = Net::DNS::Resolver->new;
626     my $query = $res->send("_gosad._tcp.".$domain, "SRV");
627     my @hits;
629     if ($query) {
630         foreach my $rr ($query->answer) {
631             push(@hits, $rr->target.":".$rr->port);
632         }
633     }
634     else {
635         #warn "query failed: ", $res->errorstring, "\n";
636         $error++;
637     }
639     if( $error == 0 ) {
640         foreach my $hit (@hits) {
641             my ($hit_name, $hit_port) = split(/:/, $hit);
643             my $address_query = $res->send($hit_name);
644             if( 1 == length($address_query->answer) ) {
645                 foreach my $rr ($address_query->answer) {
646                     push(@result, $rr->address.":".$hit_port);
647                 }
648             }
649         }
650     }
652 #    my $dig_cmd= 'dig +nocomments srv _gosad._tcp.'.$domain;
654 #    my $output= `$dig_cmd 2>&1`;
655 #    open (PIPE, "$dig_cmd 2>&1 |");
656 #    while(<PIPE>) {
657 #        chomp $_;
658 #        # If it's not a comment
659 #        if($_ =~ m/^[^;]/) {
660 #            my @matches= split /\s+/;
662 #            # Push hostname with port
663 #            if($matches[3] eq 'SRV') {
664 #                push @result, $matches[7].':'.$matches[6];
665 #            } elsif ($matches[3] eq 'A') {
666 #                my $i=0;
668 #                # Substitute the hostname with the ip address of the matching A record
669 #                foreach my $host (@result) {
670 #                    if ((split /\:/, $host)[0] eq $matches[0]) {
671 #                        $result[$i]= $matches[4].':'.(split /\:/, $host)[1];
672 #                    }
673 #                    $i++;
674 #                }
675 #            }
676 #        }
677 #    }
678 #    close(PIPE);
679     return @result;
683 ##===  FUNCTION  ================================================================
684 ##         NAME:  create_ciphering
685 ##   PARAMETERS:  passwd - string - used to create ciphering
686 ##      RETURNS:  cipher - object
687 ##  DESCRIPTION:  creates a Crypt::Rijndael::MODE_CBC object with passwd as key
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);
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 #sub create_ciphering {
703 #    my ($passwd) = @_;
704 #    $passwd = substr(md5_hex("$passwd") x 32, 0, 32);
705 #    my $iv = substr(md5_hex('GONICUS GmbH'),0, 16);
706 #    my $my_cipher = Crypt::Rijndael->new($passwd , Crypt::Rijndael::MODE_CBC());
707 #    $my_cipher->set_iv($iv);
708 #    return $my_cipher;
709 #}
712 #sub encrypt_msg {
713 #    my ($msg, $key) = @_;
714 #    my $my_cipher = &create_ciphering($key);
715 #    {
716 #      use bytes;
717 #      $msg = "\0"x(16-length($msg)%16).$msg;
718 #    }
719 #    $msg = $my_cipher->encrypt($msg);
720 #    chomp($msg = &encode_base64($msg));
721 #    # there are no newlines allowed inside msg
722 #    $msg=~ s/\n//g;
723 #    return $msg;
724 #}
727 #sub decrypt_msg {
728 #    my ($msg, $key) = @_ ;
729 #    $msg = &decode_base64($msg);
730 #    my $my_cipher = &create_ciphering($key);
731 #    $msg = $my_cipher->decrypt($msg); 
732 #    $msg =~ s/\0*//g;
733 #    return $msg;
734 #}
737 #===  FUNCTION  ================================================================
738 #         NAME:  send_msg_hash2address
739 #   PARAMETERS:  msg_hash - hash - xml_hash created with function create_xml_hash
740 #                PeerAddr string - socket address to send msg
741 #                PeerPort string - socket port, if not included in socket address
742 #      RETURNS:  nothing
743 #  DESCRIPTION:  ????
744 #===============================================================================
745 sub send_msg_hash2address {
746     my ($msg_hash, $address, $passwd) = @_ ;
748     # fetch header for logging
749     my $header = @{$msg_hash->{header}}[0];  
751     # generate xml string
752     my $msg_xml = &create_xml_string($msg_hash);
753     
754     # encrypt xml msg
755     my $crypted_msg = &encrypt_msg($msg_xml, $passwd);
757     # opensocket
758     my $socket = &open_socket($address);
759     if(not defined $socket){
760         daemon_log("cannot send '$header'-msg to $address , server not reachable", 5);
761         return 1;
762     }
763     
764     # send xml msg
765     print $socket $crypted_msg."\n";
766     
767     close $socket;
769     daemon_log("send '$header'-msg to $address", 1);
770     daemon_log("message:\n$msg_xml", 8);
771     return 0;
775 sub send_msg_to_target {
776     my ($msg, $address, $encrypt_key, $msg_header) = @_ ;
777     my $error = 0;
779     if( $msg_header ) {
780         $msg_header = "'$msg_header'-";
781     }
782     else {
783         $msg_header = "";
784     }
786     # encrypt xml msg
787     my $crypted_msg = &encrypt_msg($msg, $encrypt_key);
789     # opensocket
790     my $socket = &open_socket($address);
791     if( !$socket ) {
792         daemon_log("cannot send ".$msg_header."msg to $address , host not reachable", 1);
793         $error++;
794     }
795     
796     if( $error == 0 ) {
797         # send xml msg
798         print $socket $crypted_msg."\n";
800         daemon_log("send ".$msg_header."msg to $address", 1);
801         daemon_log("message:\n$msg", 8);
803     }
805     # close socket in any case
806     if( $socket ) {
807         close $socket;
808     }
810     return;
814 sub open_socket {
815     my ($PeerAddr, $PeerPort) = @_ ;
816     if(defined($PeerPort)){
817         $PeerAddr = $PeerAddr.":".$PeerPort;
818     }
819     my $socket;
820     $socket = new IO::Socket::INET(PeerAddr => $PeerAddr,
821             Porto => "tcp",
822             Type => SOCK_STREAM,
823             Timeout => 5,
824             );
825     if(not defined $socket) {
826         return;
827     }
828     &daemon_log("open_socket: $PeerAddr", 7);
829     return $socket;
833 #===  FUNCTION  ================================================================
834 #         NAME:  register_at_server
835 #   PARAMETERS:  
836 #      RETURNS:  
837 #  DESCRIPTION:  
838 #===============================================================================
839 sub register_at_gosa_si_server {
840     my ($kernel) = $_[KERNEL];
842     if( $REGISTERED_FLAG == 1 ) {
844         # create new passwd and ciphering object for client-server communication
845         $server_key = &create_passwd();
847         my $events = join( ", ", keys %{$event_hash} );
849         while(1) {
851             # fetch first gosa-si-server from @servers
852             my $server = shift(@servers);
854             if( !$server ) {
855                 daemon_log("no gosa-si-server left in list of servers", 1);
856                 daemon_log("unable to register at a gosa-si-server, force shutdown", 1);
857                 exit(1);
858             }
860             # create registration msg
861             my $register_hash = &create_xml_hash("here_i_am", &get_local_ip_for_remote_ip(sprintf("%s", $server =~ /^([0-9\.]*?):.*$/)).":".$client_port, $server);
862             &add_content2xml_hash($register_hash, "new_passwd", $server_key);
863                         &add_content2xml_hash($register_hash, "mac_address", &get_local_mac_for_remote_ip(sprintf("%s", $server =~ /^([0-9\.]*?):.*$/)));
864             &add_content2xml_hash($register_hash, "events", $events);
865             &add_content2xml_hash($register_hash, "gotoHardwareChecksum", $gotoHardwareChecksum);
867             # send xml hash to server with general server passwd
868             my $res = &send_msg_hash2address($register_hash, $server, $default_server_key);
869                         if($res) {
870                                 last;
871                         } else {
872                                 next;
873                         }
874         }
875         daemon_log("waiting for msg 'register_at_gosa_si_server'",1);
876         $kernel->delay_set('register_at_gosa_si_server',180);
877         # clear old settings and set it again
878         $kernel->delay_set('trigger_new_key', $server_key_lifetime);
879     }
880     return;
882     
884 sub check_key_and_xml_validity {
885     my ($crypted_msg, $module_key) = @_;
886 #print STDERR "crypted_msg:$crypted_msg\n";
887 #print STDERR "modul_key:$module_key\n";
889     my $msg;
890     my $msg_hash;
891     eval{
892         $msg = &decrypt_msg($crypted_msg, $module_key);
893         &main::daemon_log("decrypted_msg: \n$msg", 8);
895         $msg_hash = $xml->XMLin($msg, ForceArray=>1);
897         # check header
898         my $header_l = $msg_hash->{'header'};
899         if( 1 != @{$header_l} ) {
900             die 'no or more headers specified';
901         }
902         my $header = @{$header_l}[0];
903         if( 0 == length $header) {
904             die 'header has length 0';
905         }
907         # check source
908         my $source_l = $msg_hash->{'source'};
909         if( 1 != @{$source_l} ) {
910             die 'no or more sources specified';
911         }
912         my $source = @{$source_l}[0];
913         if( 0 == length $source) {
914             die 'source has length 0';
915         }
917         # check target
918         my $target_l = $msg_hash->{'target'};
919         if( 1 != @{$target_l} ) {
920             die 'no or more targets specified ';
921         }
922         my $target = @{$target_l}[0];
923         if( 0 == length $target) {
924             die 'target has length 0 ';
925         }
927     };
928     if($@) {
929         &main::daemon_log("WARNING: do not understand the message or msg is not gosa-si envelope conform:", 5);
930         &main::daemon_log("$@", 8);
931     }
933     return ($msg, $msg_hash);
937 sub import_events {
939     if (not -e $event_dir) {
940         daemon_log("ERROR: cannot find directory or directory is not readable: $event_dir", 1);   
941     }
942     opendir (DIR, $event_dir) or die "ERROR while loading gosa-si-events from directory $event_dir : $!\n";
944     while (defined (my $event = readdir (DIR))) {
945         if( $event eq "." || $event eq ".." ) { next; }    
947         eval{ require $event; };
948         if( $@ ) {
949             daemon_log("import of event module '$event' failed", 1);
950             daemon_log("$@", 8);
951             next;
952         }
954         $event =~ /(\S*?).pm$/;
955         my $event_module = $1;
956         my $events_l = eval( $1."::get_events()") ;
957         foreach my $event_name (@{$events_l}) {
958             $event_hash->{$event_name} = $event_module;
959         }
961     }
964 sub trigger_new_key {
965     my ($kernel) = $_[KERNEL] ;   
967     my $msg = "<xml><header>new_key</header><source>$client_address</source><target>$client_address</target></xml>";
968     &send_msg_to_target($msg, $client_address, $server_key, 'new_key');
970     $kernel->delay_set('trigger_new_key', $server_key_lifetime);
975 sub _start {
976     my ($kernel) = $_[KERNEL];
977     $kernel->alias_set('client_session');
978     $kernel->yield('register_at_gosa_si_server');
982 sub server_input {
983     my ($kernel, $heap, $input, $wheel) = @_[KERNEL, HEAP, ARG0, ARG1];
984     my $error = 0;
985     my $answer;
986     
987     daemon_log("Incoming msg:\n$input\n", 8);
989     my ($msg, $msg_hash) = &check_key_and_xml_validity($input, $server_key);
990     if( (!$msg) || (!$msg_hash) ) {
991         daemon_log("Deciphering of incoming msg failed", 5);
992         $error++;
993     }
995     ######################
996     # process incoming msg
997     if( $error == 0 ) {
998         my $header = @{$msg_hash->{header}}[0];
999         my $source = @{$msg_hash->{source}}[0];
1001         if( exists $event_hash->{$header} ) {
1002             # a event exists with the header as name
1003             daemon_log("found event '$header' at event-module '".$event_hash->{$header}."'", 5);
1004             no strict 'refs';
1005             $answer = &{$event_hash->{$header}."::$header"}($msg, $msg_hash);
1006          }
1007     }
1009     ########
1010     # answer
1011     if( $answer ) {
1012         # preprocessing
1013         if( $answer =~ "<header>registered</header>") {
1014             # set registered flag to true to stop sending further registered msgs
1015             $REGISTERED_FLAG = 0;
1016         } 
1017         else {
1018             &send_msg_to_target($answer, $server_address, $server_key);
1019         }
1020         # postprocessing
1021         if( $answer =~ "<header>new_key</header>") {
1022 print STDERR "old key: $server_key\n";
1023             # set new key to global variable
1024             $answer =~ /<new_key>(\S*?)<\/new_key>/;
1025             my $new_key = $1;
1026             $server_key = $new_key;
1027 print STDERR "new key: $new_key\n";
1029         }
1030     }
1032     return;
1035 #==== MAIN = main ==============================================================
1036 #  parse commandline options
1037 Getopt::Long::Configure( "bundling" );
1038 GetOptions("h|help" => \&usage,
1039            "c|config=s" => \$cfg_file,
1040            "f|foreground" => \$foreground,
1041            "v|verbose+" => \$verbose,
1042            );
1044 #  read and set config parameters
1045 &check_cmdline_param ;
1046 &read_configfile($cfg_file, %cfg_defaults);
1047 &check_pid;
1050 # forward error messages to logfile
1051 if ( ! $foreground ) {
1052         open STDIN, '/dev/null' or die "Can’t read /dev/null: $!";
1053         open STDOUT, '>>/dev/null' or die "Can't write to /dev/null: $!";
1054         open STDERR, '>>/dev/null' or die "Can't write to /dev/null: $!";
1057 # Just fork, if we are not in foreground mode
1058 if( ! $foreground ) { 
1059     chdir '/'                 or die "Can't chdir to /: $!";
1060     $pid = fork;
1061     setsid                    or die "Can't start a new session: $!";
1062     umask 0;
1063
1064 else { 
1065     $pid = $$; 
1068 # Do something useful - put our PID into the pid_file
1069 if( 0 != $pid ) {
1070     open( LOCK_FILE, ">$pid_file" );
1071     print LOCK_FILE "$pid\n";
1072     close( LOCK_FILE );
1073     if( !$foreground ) { 
1074         exit( 0 ) 
1075     };
1078 daemon_log(" ", 1);
1079 daemon_log("$0 started!", 1);
1081 # delete old DBsqlite lock files
1082 system('rm -f /tmp/gosa_si_lock*gosa-si-client*');
1085 # complete client_address
1086 $client_address = $client_ip.":".$client_port;
1089 # detect own ip and mac address
1090 my $network_interface= &get_interface_for_ip($client_ip);
1091 $client_mac_address= &get_mac($network_interface);
1092 daemon_log("gosa-si-client ip address detected: $client_ip", 1);
1093 daemon_log("gosa-si-client mac address detected: $client_mac_address", 1);
1096 # import events
1097 &import_events();
1100 # compute hardware checksum
1101 $gotoHardwareChecksum= &generate_hw_digest();
1102 daemon_log("gosa-si-client gotoHardwareChecksum detected: $gotoHardwareChecksum", 1);
1105 # create socket for incoming xml messages
1106 POE::Component::Server::TCP->new(
1107     Alias => 'gosa-si-client',
1108         Port => $client_port,
1109         ClientInput => \&server_input,
1110 );
1111 daemon_log("start socket for incoming xml messages at port '$client_port' ", 1);
1114 # prepare variables
1115 if (defined $server_ip && defined $server_port) {
1116     $server_address = $server_ip.":".$server_port;
1118 $xml = new XML::Simple();
1119 $default_server_key = $server_key;
1122 # find all possible gosa-si-servers in DNS
1123 if (defined $server_domain) {
1124     my @tmp_servers = &get_server_addresses($server_domain);
1125     foreach my $server (@tmp_servers) { 
1126         unshift(@servers, $server); 
1127     }
1129 # add gosa-si-server address from config file at first position of server list
1130 if (defined $server_address) {
1131     unshift(@servers, $server_address);
1133 my $servers_string = join(", ", @servers);
1134 daemon_log("found servers in configuration file and via DNS: $servers_string", 5);
1137 POE::Session->create(
1138         inline_states => {
1139                 _start => \&_start, 
1140         register_at_gosa_si_server => \&register_at_gosa_si_server,
1141         trigger_new_key => \&trigger_new_key,
1142         }
1143 );
1145 POE::Kernel->run();
1146 exit;