Code

update: dns-lookup can be switched of till now
[gosa.git] / gosa-si / gosa-si-server
1 #!/usr/bin/perl
2 #===============================================================================
3 #
4 #         FILE:  gosa-sd
5 #
6 #        USAGE:  ./gosa-sd
7 #
8 #  DESCRIPTION:
9 #
10 #      OPTIONS:  ---
11 # REQUIREMENTS:  libconfig-inifiles-perl libcrypt-rijndael-perl libxml-simple-perl 
12 #                libdata-dumper-simple-perl libdbd-sqlite3-perl libnet-ldap-perl
13 #                libpoe-perl
14 #         BUGS:  ---
15 #        NOTES:
16 #       AUTHOR:   (Andreas Rettenberger), <rettenberger@gonicus.de>
17 #      COMPANY:
18 #      VERSION:  1.0
19 #      CREATED:  12.09.2007 08:54:41 CEST
20 #     REVISION:  ---
21 #===============================================================================
24 # TODO
25 #
26 # max_children wird momentan nicht mehr verwendet, jede eingehende nachricht bekommt ein eigenes POE child
28 use strict;
29 use warnings;
30 use Getopt::Long;
31 use Config::IniFiles;
32 use POSIX;
34 use Fcntl;
35 use IO::Socket::INET;
36 use IO::Handle;
37 use IO::Select;
38 use Symbol qw(qualify_to_ref);
39 use Crypt::Rijndael;
40 use MIME::Base64;
41 use Digest::MD5  qw(md5 md5_hex md5_base64);
42 use XML::Simple;
43 use Data::Dumper;
44 use Sys::Syslog qw( :DEFAULT setlogsock);
45 use Cwd;
46 use File::Spec;
47 use File::Basename;
48 use File::Find;
49 use File::Copy;
50 use File::Path;
51 use GOSA::DBsqlite;
52 use GOSA::GosaSupportDaemon;
53 use POE qw(Component::Server::TCP Wheel::Run Filter::Reference);
54 use Net::LDAP;
55 use Net::LDAP::Util qw(:escape);
56 use Time::HiRes qw( usleep);
58 my $modules_path = "/usr/lib/gosa-si/modules";
59 use lib "/usr/lib/gosa-si/modules";
61 # revision number of server and program name
62 my $server_version = '$HeadURL: https://oss.gonicus.de/repositories/gosa/trunk/gosa-si/gosa-si-server $:$Rev: 10826 $';
63 my $server_headURL;
64 my $server_revision;
65 my $server_status;
66 our $prg= basename($0);
68 our $global_kernel;
69 my ($foreground, $ping_timeout);
70 my ($server);
71 my ($gosa_server, $job_queue_timeout, $job_queue_loop_delay);
72 my ($messaging_db_loop_delay);
73 my ($procid, $pid);
74 my ($arp_fifo);
75 my ($xml);
76 my $sources_list;
77 my $max_clients;
78 my %repo_files=();
79 my $repo_path;
80 my %repo_dirs=();
82 # Variables declared in config file are always set to 'our'
83 our (%cfg_defaults, $log_file, $pid_file, 
84     $server_ip, $server_port, $ClientPackages_key, $dns_lookup,
85     $arp_activ, $gosa_unit_tag,
86     $GosaPackages_key, $gosa_timeout,
87     $foreign_server_string, $server_domain, $ServerPackages_key, $foreign_servers_register_delay,
88     $wake_on_lan_passwd, $job_synchronization, $modified_jobs_loop_delay,
89     $arp_enabled, $arp_interface,
90     $opsi_enabled, $opsi_server, $opsi_admin, $opsi_password,
91                 $new_systems_ou,
92 );
94 # additional variable which should be globaly accessable
95 our $server_address;
96 our $server_mac_address;
97 our $gosa_address;
98 our $no_arp;
99 our $verbose;
100 our $forground;
101 our $cfg_file;
102 our ($ldap_uri, $ldap_base, $ldap_admin_dn, $ldap_admin_password, $ldap_server_dn);
103 our $known_modules;
105 # specifies the verbosity of the daemon_log
106 $verbose = 0 ;
108 # if foreground is not null, script will be not forked to background
109 $foreground = 0 ;
111 # specifies the timeout seconds while checking the online status of a registrating client
112 $ping_timeout = 5;
114 $no_arp = 0;
115 my $packages_list_under_construction = "/tmp/packages_list_creation_in_progress";
116 my @packages_list_statements;
117 my $watch_for_new_jobs_in_progress = 0;
119 # holds all incoming decrypted messages
120 our $incoming_db;
121 our $incoming_tn = 'incoming';
122 my $incoming_file_name;
123 my @incoming_col_names = ("id INTEGER PRIMARY KEY", 
124         "timestamp DEFAULT 'none'", 
125         "headertag DEFAULT 'none'",
126                 "targettag DEFAULT 'none'",
127         "xmlmessage DEFAULT 'none'",
128         "module DEFAULT 'none'",
129         "sessionid DEFAULT '0'",
130         );
132 # holds all gosa jobs
133 our $job_db;
134 our $job_queue_tn = 'jobs';
135 my $job_queue_file_name;
136 my @job_queue_col_names = ("id INTEGER PRIMARY KEY", 
137                 "timestamp DEFAULT 'none'", 
138                 "status DEFAULT 'none'", 
139                 "result DEFAULT 'none'", 
140                 "progress DEFAULT 'none'", 
141         "headertag DEFAULT 'none'", 
142                 "targettag DEFAULT 'none'", 
143                 "xmlmessage DEFAULT 'none'", 
144                 "macaddress DEFAULT 'none'",
145                 "plainname DEFAULT 'none'",
146         "siserver DEFAULT 'none'",
147         "modified DEFAULT '0'",
148                 );
150 # holds all other gosa-si-server
151 our $known_server_db;
152 our $known_server_tn = "known_server";
153 my $known_server_file_name;
154 my @known_server_col_names = ("hostname", "macaddress", "status", "hostkey", "loaded_modules", "timestamp");
156 # holds all registrated clients
157 our $known_clients_db;
158 our $known_clients_tn = "known_clients";
159 my $known_clients_file_name;
160 my @known_clients_col_names = ("hostname", "status", "hostkey", "timestamp", "macaddress", "events", "keylifetime");
162 # holds all registered clients at a foreign server
163 our $foreign_clients_db;
164 our $foreign_clients_tn = "foreign_clients"; 
165 my $foreign_clients_file_name;
166 my @foreign_clients_col_names = ("hostname", "macaddress", "regserver", "timestamp");
168 # holds all logged in user at each client 
169 our $login_users_db;
170 our $login_users_tn = "login_users";
171 my $login_users_file_name;
172 my @login_users_col_names = ("client", "user", "timestamp");
174 # holds all fai server, the debian release and tag
175 our $fai_server_db;
176 our $fai_server_tn = "fai_server"; 
177 my $fai_server_file_name;
178 our @fai_server_col_names = ("timestamp", "server", "release", "sections", "tag"); 
180 our $fai_release_db;
181 our $fai_release_tn = "fai_release"; 
182 my $fai_release_file_name;
183 our @fai_release_col_names = ("timestamp", "release", "class", "type", "state"); 
185 # holds all packages available from different repositories
186 our $packages_list_db;
187 our $packages_list_tn = "packages_list";
188 my $packages_list_file_name;
189 our @packages_list_col_names = ("distribution", "package", "version", "section", "description", "template", "timestamp");
190 my $outdir = "/tmp/packages_list_db";
191 my $arch = "i386"; 
193 # holds all messages which should be delivered to a user
194 our $messaging_db;
195 our $messaging_tn = "messaging"; 
196 our @messaging_col_names = ("id INTEGER", "subject", "message_from", "message_to", 
197         "flag", "direction", "delivery_time", "message", "timestamp" );
198 my $messaging_file_name;
200 # path to directory to store client install log files
201 our $client_fai_log_dir = "/var/log/fai"; 
203 # queue which stores taskes until one of the $max_children children are ready to process the task
204 my @tasks = qw();
205 my @msgs_to_decrypt = qw();
206 my $max_children = 2;
209 # loop delay for job queue to look for opsi jobs
210 my $job_queue_opsi_delay = 10;
211 our $opsi_client;
212 our $opsi_url;
213  
214 # Lifetime of logged in user information. If no update information comes after n seconds, 
215 # the user is expeceted to be no longer logged in or the host is no longer running. Because
216 # of this, the user is deleted from login_users_db
217 our $logged_in_user_date_of_expiry = 600;
220 %cfg_defaults = (
221 "general" => {
222     "log-file" => [\$log_file, "/var/run/".$prg.".log"],
223     "pid-file" => [\$pid_file, "/var/run/".$prg.".pid"],
224     },
225 "server" => {
226     "ip"                    => [\$server_ip, "0.0.0.0"],
227     "port"                  => [\$server_port, "20081"],
228     "known-clients"         => [\$known_clients_file_name, '/var/lib/gosa-si/clients.db' ],
229     "known-servers"         => [\$known_server_file_name, '/var/lib/gosa-si/servers.db'],
230     "incoming"              => [\$incoming_file_name, '/var/lib/gosa-si/incoming.db'],
231     "login-users"           => [\$login_users_file_name, '/var/lib/gosa-si/users.db'],
232     "fai-server"            => [\$fai_server_file_name, '/var/lib/gosa-si/fai_server.db'],
233     "fai-release"           => [\$fai_release_file_name, '/var/lib/gosa-si/fai_release.db'],
234     "packages-list"         => [\$packages_list_file_name, '/var/lib/gosa-si/packages.db'],
235     "messaging"             => [\$messaging_file_name, '/var/lib/gosa-si/messaging.db'],
236     "foreign-clients"       => [\$foreign_clients_file_name, '/var/lib/gosa-si/foreign_clients.db'],
237     "source-list"           => [\$sources_list, '/etc/apt/sources.list'],
238     "repo-path"             => [\$repo_path, '/srv/www/repository'],
239     "ldap-uri"              => [\$ldap_uri, ""],
240     "ldap-base"             => [\$ldap_base, ""],
241     "ldap-admin-dn"         => [\$ldap_admin_dn, ""],
242     "ldap-admin-password"   => [\$ldap_admin_password, ""],
243     "gosa-unit-tag"         => [\$gosa_unit_tag, ""],
244     "max-clients"           => [\$max_clients, 10],
245     "wol-password"          => [\$wake_on_lan_passwd, ""],
246     },
247 "GOsaPackages" => {
248     "job-queue" => [\$job_queue_file_name, '/var/lib/gosa-si/jobs.db'],
249     "job-queue-loop-delay" => [\$job_queue_loop_delay, 3],
250     "messaging-db-loop-delay" => [\$messaging_db_loop_delay, 3],
251     "key" => [\$GosaPackages_key, "none"],
252                 "new-systems-ou" => [\$new_systems_ou, 'ou=workstations,ou=systems'],
253     },
254 "ClientPackages" => {
255     "key" => [\$ClientPackages_key, "none"],
256     "user-date-of-expiry" => [\$logged_in_user_date_of_expiry, 600],
257     },
258 "ServerPackages"=> {
259     "address"      => [\$foreign_server_string, ""],
260     "dns-lookup"            => [\$dns_lookup, "true"],
261     "domain"  => [\$server_domain, ""],
262     "key"     => [\$ServerPackages_key, "none"],
263     "key-lifetime" => [\$foreign_servers_register_delay, 120],
264     "job-synchronization-enabled" => [\$job_synchronization, "true"],
265     "synchronization-loop" => [\$modified_jobs_loop_delay, 5],
266     },
267 "ArpHandler" => {
268     "enabled"   => [\$arp_enabled, "true"],
269     "interface" => [\$arp_interface, "all"],
270         },
271 "Opsi" => {
272     "enabled"  => [\$opsi_enabled, "false"], 
273     "server"   => [\$opsi_server, "localhost"],
274     "admin"    => [\$opsi_admin, "opsi-admin"],
275     "password" => [\$opsi_password, "secret"],
276    },
278 );
281 #===  FUNCTION  ================================================================
282 #         NAME:  usage
283 #   PARAMETERS:  nothing
284 #      RETURNS:  nothing
285 #  DESCRIPTION:  print out usage text to STDERR
286 #===============================================================================
287 sub usage {
288     print STDERR << "EOF" ;
289 usage: $prg [-hvf] [-c config]
291            -h        : this (help) message
292            -c <file> : config file
293            -f        : foreground, process will not be forked to background
294            -v        : be verbose (multiple to increase verbosity)
295            -no-arp   : starts $prg without connection to arp module
296  
297 EOF
298     print "\n" ;
302 #===  FUNCTION  ================================================================
303 #         NAME:  logging
304 #   PARAMETERS:  level - string - default 'info'
305 #                msg - string -
306 #                facility - string - default 'LOG_DAEMON'
307 #      RETURNS:  nothing
308 #  DESCRIPTION:  function for logging
309 #===============================================================================
310 sub daemon_log {
311     # log into log_file
312     my( $msg, $level ) = @_;
313     if(not defined $msg) { return }
314     if(not defined $level) { $level = 1 }
315     if(defined $log_file){
316         open(LOG_HANDLE, ">>$log_file");
317         chmod 0600, $log_file;
318         if(not defined open( LOG_HANDLE, ">>$log_file" )) {
319             print STDERR "cannot open $log_file: $!";
320             return 
321         }
322         chomp($msg);
323         #$msg =~s/\n//g;   # no newlines are allowed in log messages, this is important for later log parsing
324         if($level <= $verbose){
325             my ($seconds, $minutes, $hours, $monthday, $month,
326                     $year, $weekday, $yearday, $sommertime) = localtime(time);
327             $hours = $hours < 10 ? $hours = "0".$hours : $hours;
328             $minutes = $minutes < 10 ? $minutes = "0".$minutes : $minutes;
329             $seconds = $seconds < 10 ? $seconds = "0".$seconds : $seconds;
330             my @monthnames = ("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec");
331             $month = $monthnames[$month];
332             $monthday = $monthday < 10 ? $monthday = "0".$monthday : $monthday;
333             $year+=1900;
334             my $name = $prg;
336             my $log_msg = "$month $monthday $hours:$minutes:$seconds $name $msg\n";
337             print LOG_HANDLE $log_msg;
338             if( $foreground ) { 
339                 print STDERR $log_msg;
340             }
341         }
342         close( LOG_HANDLE );
343     }
347 #===  FUNCTION  ================================================================
348 #         NAME:  check_cmdline_param
349 #   PARAMETERS:  nothing
350 #      RETURNS:  nothing
351 #  DESCRIPTION:  validates commandline parameter
352 #===============================================================================
353 sub check_cmdline_param () {
354     my $err_config;
355     my $err_counter = 0;
356         if(not defined($cfg_file)) {
357                 $cfg_file = "/etc/gosa-si/server.conf";
358                 if(! -r $cfg_file) {
359                         $err_config = "please specify a config file";
360                         $err_counter += 1;
361                 }
362     }
363     if( $err_counter > 0 ) {
364         &usage( "", 1 );
365         if( defined( $err_config)) { print STDERR "$err_config\n"}
366         print STDERR "\n";
367         exit( -1 );
368     }
372 #===  FUNCTION  ================================================================
373 #         NAME:  check_pid
374 #   PARAMETERS:  nothing
375 #      RETURNS:  nothing
376 #  DESCRIPTION:  handels pid processing
377 #===============================================================================
378 sub check_pid {
379     $pid = -1;
380     # Check, if we are already running
381     if( open(LOCK_FILE, "<$pid_file") ) {
382         $pid = <LOCK_FILE>;
383         if( defined $pid ) {
384             chomp( $pid );
385             if( -f "/proc/$pid/stat" ) {
386                 my($stat) = `cat /proc/$pid/stat` =~ m/$pid \((.+)\).*/;
387                 if( $stat ) {
388                                         daemon_log("ERROR: Already running",1);
389                     close( LOCK_FILE );
390                     exit -1;
391                 }
392             }
393         }
394         close( LOCK_FILE );
395         unlink( $pid_file );
396     }
398     # create a syslog msg if it is not to possible to open PID file
399     if (not sysopen(LOCK_FILE, $pid_file, O_WRONLY|O_CREAT|O_EXCL, 0644)) {
400         my($msg) = "Couldn't obtain lockfile '$pid_file' ";
401         if (open(LOCK_FILE, '<', $pid_file)
402                 && ($pid = <LOCK_FILE>))
403         {
404             chomp($pid);
405             $msg .= "(PID $pid)\n";
406         } else {
407             $msg .= "(unable to read PID)\n";
408         }
409         if( ! ($foreground) ) {
410             openlog( $0, "cons,pid", "daemon" );
411             syslog( "warning", $msg );
412             closelog();
413         }
414         else {
415             print( STDERR " $msg " );
416         }
417         exit( -1 );
418     }
421 #===  FUNCTION  ================================================================
422 #         NAME:  import_modules
423 #   PARAMETERS:  module_path - string - abs. path to the directory the modules 
424 #                are stored
425 #      RETURNS:  nothing
426 #  DESCRIPTION:  each file in module_path which ends with '.pm' and activation 
427 #                state is on is imported by "require 'file';"
428 #===============================================================================
429 sub import_modules {
430     daemon_log(" ", 1);
432     if (not -e $modules_path) {
433         daemon_log("0 ERROR: cannot find directory or directory is not readable: $modules_path", 1);   
434     }
436     opendir (DIR, $modules_path) or die "ERROR while loading modules from directory $modules_path : $!\n";
437     while (defined (my $file = readdir (DIR))) {
438         if (not $file =~ /(\S*?).pm$/) {
439             next;
440         }
441                 my $mod_name = $1;
443         # ArpHandler switch
444         if( $file =~ /ArpHandler.pm/ ) {
445             if( $arp_enabled eq "false" ) { next; }
446         }
447         
448         eval { require $file; };
449         if ($@) {
450             daemon_log("0 ERROR: gosa-si-server could not load module $file", 1);
451             daemon_log("$@", 5);
452                 } else {
453                         my $info = eval($mod_name.'::get_module_info()');
454                         # Only load module if get_module_info() returns a non-null object
455                         if( $info ) {
456                                 my ($input_address, $input_key, $event_hash) = @{$info};
457                                 $known_modules->{$mod_name} = $info;
458                                 daemon_log("0 INFO: module $mod_name loaded", 5);
459                         }
460                 }
461     }   
463     close (DIR);
466 #===  FUNCTION  ================================================================
467 #         NAME:  password_check
468 #   PARAMETERS:  nothing
469 #      RETURNS:  nothing
470 #  DESCRIPTION:  escalates an critical error if two modules exist which are avaialable by 
471 #                the same password
472 #===============================================================================
473 sub password_check {
474     my $passwd_hash = {};
475     while (my ($mod_name, $mod_info) = each %$known_modules) {
476         my $mod_passwd = @$mod_info[1];
477         if (not defined $mod_passwd) { next; }
478         if (not exists $passwd_hash->{$mod_passwd}) {
479             $passwd_hash->{$mod_passwd} = $mod_name;
481         # escalates critical error
482         } else {
483             &daemon_log("0 ERROR: two loaded modules do have the same password. Please modify the 'key'-parameter in config file");
484             &daemon_log("0 ERROR: module='$mod_name' and module='".$passwd_hash->{$mod_passwd}."'");
485             exit( -1 );
486         }
487     }
492 #===  FUNCTION  ================================================================
493 #         NAME:  sig_int_handler
494 #   PARAMETERS:  signal - string - signal arose from system
495 #      RETURNS:  nothing
496 #  DESCRIPTION:  handels tasks to be done befor signal becomes active
497 #===============================================================================
498 sub sig_int_handler {
499     my ($signal) = @_;
501 #       if (defined($ldap_handle)) {
502 #               $ldap_handle->disconnect;
503 #       }
504     # TODO alle verbliebenden ldap verbindungen aus allen heaps beenden
505     
507     daemon_log("shutting down gosa-si-server", 1);
508     system("kill `ps -C gosa-si-server -o pid=`");
510 $SIG{INT} = \&sig_int_handler;
513 sub check_key_and_xml_validity {
514     my ($crypted_msg, $module_key, $session_id) = @_;
515     my $msg;
516     my $msg_hash;
517     my $error_string;
518     eval{
519         $msg = &decrypt_msg($crypted_msg, $module_key);
521         if ($msg =~ /<xml>/i){
522             $msg =~ s/\s+/ /g;  # just for better daemon_log
523             daemon_log("$session_id DEBUG: decrypted_msg: \n$msg", 8);
524             $msg_hash = $xml->XMLin($msg, ForceArray=>1);
526             ##############
527             # check header
528             if( not exists $msg_hash->{'header'} ) { die "no header specified"; }
529             my $header_l = $msg_hash->{'header'};
530             if( 1 > @{$header_l} ) { die 'empty header tag'; }
531             if( 1 < @{$header_l} ) { die 'more than one header specified'; }
532             my $header = @{$header_l}[0];
533             if( 0 == length $header) { die 'empty string in header tag'; }
535             ##############
536             # check source
537             if( not exists $msg_hash->{'source'} ) { die "no source specified"; }
538             my $source_l = $msg_hash->{'source'};
539             if( 1 > @{$source_l} ) { die 'empty source tag'; }
540             if( 1 < @{$source_l} ) { die 'more than one source specified'; }
541             my $source = @{$source_l}[0];
542             if( 0 == length $source) { die 'source error'; }
544             ##############
545             # check target
546             if( not exists $msg_hash->{'target'} ) { die "no target specified"; }
547             my $target_l = $msg_hash->{'target'};
548             if( 1 > @{$target_l} ) { die 'empty target tag'; }
549         }
550     };
551     if($@) {
552         daemon_log("$session_id ERROR: do not understand the message: $@", 1);
553         $msg = undef;
554         $msg_hash = undef;
555     }
557     return ($msg, $msg_hash);
561 sub check_outgoing_xml_validity {
562     my ($msg, $session_id) = @_;
564     my $msg_hash;
565     eval{
566         $msg_hash = $xml->XMLin($msg, ForceArray=>1);
568         ##############
569         # check header
570         my $header_l = $msg_hash->{'header'};
571         if( 1 != @{$header_l} ) {
572             die 'no or more than one headers specified';
573         }
574         my $header = @{$header_l}[0];
575         if( 0 == length $header) {
576             die 'header has length 0';
577         }
579         ##############
580         # check source
581         my $source_l = $msg_hash->{'source'};
582         if( 1 != @{$source_l} ) {
583             die 'no or more than 1 sources specified';
584         }
585         my $source = @{$source_l}[0];
586         if( 0 == length $source) {
587             die 'source has length 0';
588         }
589         unless( $source =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
590                 $source =~ /^GOSA$/i ) {
591             die "source '$source' is neither a complete ip-address with port nor 'GOSA'";
592         }
593         
594         ##############
595         # check target  
596         my $target_l = $msg_hash->{'target'};
597         if( 0 == @{$target_l} ) {
598             die "no targets specified";
599         }
600         foreach my $target (@$target_l) {
601             if( 0 == length $target) {
602                 die "target has length 0";
603             }
604             unless( $target =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
605                     $target =~ /^GOSA$/i ||
606                     $target =~ /^\*$/ ||
607                     $target =~ /KNOWN_SERVER/i ||
608                     $target =~ /JOBDB/i ||
609                     $target =~ /^([0-9a-f]{2}:[0-9a-f]{2}:[0-9a-f]{2}:[0-9a-f]{2}:[0-9a-f]{2}:[0-9a-f]{2})$/i ){
610                 die "target '$target' is not a complete ip-address with port or a valid target name or a mac-address";
611             }
612         }
613     };
614     if($@) {
615         daemon_log("$session_id ERROR: outgoing msg is not gosa-si envelope conform: $@", 1);
616         daemon_log("$@ ".(defined($msg) && length($msg)>0)?$msg:"Empty Message", 1);
617         $msg_hash = undef;
618     }
620     return ($msg_hash);
624 sub input_from_known_server {
625     my ($input, $remote_ip, $session_id) = @_ ;  
626     my ($msg, $msg_hash, $module);
628     my $sql_statement= "SELECT * FROM known_server";
629     my $query_res = $known_server_db->select_dbentry( $sql_statement ); 
631     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
632         my $host_name = $hit->{hostname};
633         if( not $host_name =~ "^$remote_ip") {
634             next;
635         }
636         my $host_key = $hit->{hostkey};
637         daemon_log("$session_id DEBUG: input_from_known_server: host_name: $host_name", 7);
638         daemon_log("$session_id DEBUG: input_from_known_server: host_key: $host_key", 7);
640         # check if module can open msg envelope with module key
641         my ($tmp_msg, $tmp_msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
642         if( (!$tmp_msg) || (!$tmp_msg_hash) ) {
643             daemon_log("$session_id DEBUG: input_from_known_server: deciphering raise error", 7);
644             daemon_log("$@", 8);
645             next;
646         }
647         else {
648             $msg = $tmp_msg;
649             $msg_hash = $tmp_msg_hash;
650             $module = "ServerPackages";
651             last;
652         }
653     }
655     if( (!$msg) || (!$msg_hash) || (!$module) ) {
656         daemon_log("$session_id DEBUG: Incoming message is not from a known server", 7);
657     }
658   
659     return ($msg, $msg_hash, $module);
663 sub input_from_known_client {
664     my ($input, $remote_ip, $session_id) = @_ ;  
665     my ($msg, $msg_hash, $module);
667     my $sql_statement= "SELECT * FROM known_clients";
668     my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
669     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
670         my $host_name = $hit->{hostname};
671         if( not $host_name =~ /^$remote_ip:\d*$/) {
672                 next;
673                 }
674         my $host_key = $hit->{hostkey};
675         &daemon_log("$session_id DEBUG: input_from_known_client: host_name: $host_name", 7);
676         &daemon_log("$session_id DEBUG: input_from_known_client: host_key: $host_key", 7);
678         # check if module can open msg envelope with module key
679         ($msg, $msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
681         if( (!$msg) || (!$msg_hash) ) {
682             &daemon_log("$session_id DEGUG: input_from_known_client: deciphering raise error", 7);
683             &daemon_log("$@", 8);
684             next;
685         }
686         else {
687             $module = "ClientPackages";
688             last;
689         }
690     }
692     if( (!$msg) || (!$msg_hash) || (!$module) ) {
693         &daemon_log("$session_id DEBUG: Incoming message is not from a known client", 7);
694     }
696     return ($msg, $msg_hash, $module);
700 sub input_from_unknown_host {
701         no strict "refs";
702         my ($input, $session_id) = @_ ;
703         my ($msg, $msg_hash, $module);
704         my $error_string;
706         my %act_modules = %$known_modules;
708         while( my ($mod, $info) = each(%act_modules)) {
710                 # check a key exists for this module
711                 my $module_key = ${$mod."_key"};
712                 if( not defined $module_key ) {
713                         if( $mod eq 'ArpHandler' ) {
714                                 next;
715                         }
716                         daemon_log("$session_id ERROR: no key specified in config file for $mod", 1);
717                         next;
718                 }
719                 daemon_log("$session_id DEBUG: $mod: $module_key", 7);
721                 # check if module can open msg envelope with module key
722                 ($msg, $msg_hash) = &check_key_and_xml_validity($input, $module_key, $session_id);
723                 if( (not defined $msg) || (not defined $msg_hash) ) {
724                         daemon_log("$session_id ERROR: no msg returned!", 2) if ((not defined $msg) || "" eq $msg);
725                         daemon_log("$session_id ERROR: no msg_hash returned!", 2) if ((not defined $msg_hash) || "" eq $msg_hash);
726                         next;
727                 } else {
728                         $module = $mod;
729                         last;
730                 }
731         }
733         if( (!$msg) || (!$msg_hash) || (!$module)) {
734                 daemon_log("$session_id DEBUG: Incoming message is not from an unknown host", 7);
735         }
737         return ($msg, $msg_hash, $module);
741 sub create_ciphering {
742     my ($passwd) = @_;
743         if((!defined($passwd)) || length($passwd)==0) {
744                 $passwd = "";
745         }
746     $passwd = substr(md5_hex("$passwd") x 32, 0, 32);
747     my $iv = substr(md5_hex('GONICUS GmbH'),0, 16);
748     my $my_cipher = Crypt::Rijndael->new($passwd , Crypt::Rijndael::MODE_CBC());
749     $my_cipher->set_iv($iv);
750     return $my_cipher;
754 sub encrypt_msg {
755     my ($msg, $key) = @_;
756     my $my_cipher = &create_ciphering($key);
757     my $len;
758     {
759             use bytes;
760             $len= 16-length($msg)%16;
761     }
762     $msg = "\0"x($len).$msg;
763     $msg = $my_cipher->encrypt($msg);
764     chomp($msg = &encode_base64($msg));
765     # there are no newlines allowed inside msg
766     $msg=~ s/\n//g;
767     return $msg;
771 sub decrypt_msg {
773     my ($msg, $key) = @_ ;
774     $msg = &decode_base64($msg);
775     my $my_cipher = &create_ciphering($key);
776     $msg = $my_cipher->decrypt($msg); 
777     $msg =~ s/\0*//g;
778     return $msg;
782 sub get_encrypt_key {
783     my ($target) = @_ ;
784     my $encrypt_key;
785     my $error = 0;
787     # target can be in known_server
788     if( not defined $encrypt_key ) {
789         my $sql_statement= "SELECT * FROM known_server WHERE hostname='$target'";
790         my $query_res = $known_server_db->select_dbentry( $sql_statement ); 
791         while( my ($hit_num, $hit) = each %{ $query_res } ) {    
792             my $host_name = $hit->{hostname};
793             if( $host_name ne $target ) {
794                 next;
795             }
796             $encrypt_key = $hit->{hostkey};
797             last;
798         }
799     }
801     # target can be in known_client
802     if( not defined $encrypt_key ) {
803         my $sql_statement= "SELECT * FROM known_clients WHERE hostname='$target'";
804         my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
805         while( my ($hit_num, $hit) = each %{ $query_res } ) {    
806             my $host_name = $hit->{hostname};
807             if( $host_name ne $target ) {
808                 next;
809             }
810             $encrypt_key = $hit->{hostkey};
811             last;
812         }
813     }
815     return $encrypt_key;
819 #===  FUNCTION  ================================================================
820 #         NAME:  open_socket
821 #   PARAMETERS:  PeerAddr string something like 192.168.1.1 or 192.168.1.1:10000
822 #                [PeerPort] string necessary if port not appended by PeerAddr
823 #      RETURNS:  socket IO::Socket::INET
824 #  DESCRIPTION:  open a socket to PeerAddr
825 #===============================================================================
826 sub open_socket {
827     my ($PeerAddr, $PeerPort) = @_ ;
828     if(defined($PeerPort)){
829         $PeerAddr = $PeerAddr.":".$PeerPort;
830     }
831     my $socket;
832     $socket = new IO::Socket::INET(PeerAddr => $PeerAddr,
833             Porto => "tcp",
834             Type => SOCK_STREAM,
835             Timeout => 5,
836             );
837     if(not defined $socket) {
838         return;
839     }
840 #    &daemon_log("DEBUG: open_socket: $PeerAddr", 7);
841     return $socket;
845 #sub get_local_ip_for_remote_ip {
846 #       my $remote_ip= shift;
847 #       my $result="0.0.0.0";
849 #       if($remote_ip =~ /^(\d\d?\d?\.){3}\d\d?\d?$/) {
850 #               if($remote_ip eq "127.0.0.1") {
851 #                       $result = "127.0.0.1";
852 #               } else {
853 #                       my $PROC_NET_ROUTE= ('/proc/net/route');
855 #                       open(PROC_NET_ROUTE, "<$PROC_NET_ROUTE")
856 #                               or die "Could not open $PROC_NET_ROUTE";
858 #                       my @ifs = <PROC_NET_ROUTE>;
860 #                       close(PROC_NET_ROUTE);
862 #                       # Eat header line
863 #                       shift @ifs;
864 #                       chomp @ifs;
865 #                       foreach my $line(@ifs) {
866 #                               my ($Iface,$Destination,$Gateway,$Flags,$RefCnt,$Use,$Metric,$Mask,$MTU,$Window,$IRTT)=split(/\s/, $line);
867 #                               my $destination;
868 #                               my $mask;
869 #                               my ($d,$c,$b,$a)=unpack('a2 a2 a2 a2', $Destination);
870 #                               $destination= sprintf("%d.%d.%d.%d", hex($a), hex($b), hex($c), hex($d));
871 #                               ($d,$c,$b,$a)=unpack('a2 a2 a2 a2', $Mask);
872 #                               $mask= sprintf("%d.%d.%d.%d", hex($a), hex($b), hex($c), hex($d));
873 #                               if(new NetAddr::IP($remote_ip)->within(new NetAddr::IP($destination, $mask))) {
874 #                                       # destination matches route, save mac and exit
875 #                                       $result= &get_ip($Iface);
876 #                                       last;
877 #                               }
878 #                       }
879 #               }
880 #       } else {
881 #               daemon_log("0 WARNING: get_local_ip_for_remote_ip() was called with a non-ip parameter: '$remote_ip'", 1);
882 #       }
883 #       return $result;
884 #}
887 sub send_msg_to_target {
888     my ($msg, $address, $encrypt_key, $msg_header, $session_id) = @_ ;
889     my $error = 0;
890     my $header;
891     my $timestamp = &get_time();
892     my $new_status;
893     my $act_status;
894     my ($sql_statement, $res);
895   
896     if( $msg_header ) {
897         $header = "'$msg_header'-";
898     } else {
899         $header = "";
900     }
902         # Patch the source ip
903         if($msg =~ /<source>0\.0\.0\.0:\d*?<\/source>/) {
904                 my $remote_ip = &get_local_ip_for_remote_ip(sprintf("%s", $address =~ /^([0-9\.]*?):.*$/));
905                 $msg =~ s/<source>(0\.0\.0\.0):(\d*?)<\/source>/<source>$remote_ip:$2<\/source>/s;
906         }
908     # encrypt xml msg
909     my $crypted_msg = &encrypt_msg($msg, $encrypt_key);
911     # opensocket
912     my $socket = &open_socket($address);
913     if( !$socket ) {
914         daemon_log("$session_id WARNING: cannot send ".$header."msg to $address , host not reachable", 3);
915         $error++;
916     }
917     
918     if( $error == 0 ) {
919         # send xml msg
920         print $socket $crypted_msg."\n";
922         daemon_log("$session_id INFO: send ".$header."msg to $address", 5);
923         daemon_log("$session_id DEBUG: message:\n$msg", 9);
924         
925     }
927     # close socket in any case
928     if( $socket ) {
929         close $socket;
930     }
932     if( $error > 0 ) { $new_status = "down"; }
933     else { $new_status = $msg_header; }
936     # known_clients
937     $sql_statement = "SELECT * FROM $known_clients_tn WHERE hostname='$address'";
938     $res = $known_clients_db->select_dbentry($sql_statement);
939     if( keys(%$res) == 1) {
940         $act_status = exists $res->{1}->{'status'} ? $res->{1}->{'status'} : "";
941         if ($act_status eq "down" && $new_status eq "down") {
942             $sql_statement = "DELETE FROM known_clients WHERE hostname='$address'";
943             $res = $known_clients_db->del_dbentry($sql_statement);
944             daemon_log("$session_id WARNING: failed 2x to send msg to host '$address', delete host from known_clients", 3);
945         } else { 
946             $sql_statement = "UPDATE known_clients SET status='$new_status', timestamp='$timestamp' WHERE hostname='$address'";
947             $res = $known_clients_db->update_dbentry($sql_statement);
948             if($new_status eq "down"){
949                 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
950             } else {
951                 daemon_log("$session_id INFO: set '$address' from status '$act_status' to '$new_status'", 5);
952             }
953         }
954     }
956     # known_server
957     $sql_statement = "SELECT * FROM $known_server_tn WHERE hostname='$address'";
958     $res = $known_server_db->select_dbentry($sql_statement);
959     if( keys(%$res) == 1) {
960         $act_status = exists $res->{1}->{'status'} ? $res->{1}->{'status'} : "";
961         if ($act_status eq "down" && $new_status eq "down") {
962             $sql_statement = "DELETE FROM known_server WHERE hostname='$address'";
963             $res = $known_server_db->del_dbentry($sql_statement);
964             daemon_log("$session_id WARNING: failed 2x to send a message to host '$address', delete host from known_server", 3);
965         } 
966         else { 
967             $sql_statement = "UPDATE known_server SET status='$new_status', timestamp='$timestamp' WHERE hostname='$address'";
968             $res = $known_server_db->update_dbentry($sql_statement);
969             if($new_status eq "down"){
970                 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
971             } else {
972                 daemon_log("$session_id INFO: set '$address' from status '$act_status' to '$new_status'", 5);
973             }
974         }
975     }
976     return $error; 
980 sub update_jobdb_status_for_send_msgs {
981     my ($answer, $error) = @_;
982     if( $answer =~ /<jobdb_id>(\d+)<\/jobdb_id>/ ) {
983         my $jobdb_id = $1;
984             
985         # sending msg faild
986         if( $error ) {
987             if (not $answer =~ /<header>trigger_action_reinstall<\/header>/) {
988                 my $sql_statement = "UPDATE $job_queue_tn ".
989                     "SET status='error', result='can not deliver msg, please consult log file' ".
990                     "WHERE id=$jobdb_id";
991                 my $res = $job_db->update_dbentry($sql_statement);
992             }
994         # sending msg was successful
995         } else {
996             my $sql_statement = "UPDATE $job_queue_tn ".
997                 "SET status='done' ".
998                 "WHERE id=$jobdb_id AND status='processed'";
999             my $res = $job_db->update_dbentry($sql_statement);
1000         }
1001     }
1005 sub sig_handler {
1006         my ($kernel, $signal) = @_[KERNEL, ARG0] ;
1007         daemon_log("0 INFO got signal '$signal'", 1); 
1008         $kernel->sig_handled();
1009         return;
1013 sub msg_to_decrypt {
1014     my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
1015     my $session_id = $session->ID;
1016     my ($msg, $msg_hash, $module);
1017     my $error = 0;
1019     # hole neue msg aus @msgs_to_decrypt
1020     my $next_msg = shift @msgs_to_decrypt;
1021     
1022     # entschlüssle sie
1024     # msg is from a new client or gosa
1025     ($msg, $msg_hash, $module) = &input_from_unknown_host($next_msg, $session_id);
1026     # msg is from a gosa-si-server
1027     if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1028         ($msg, $msg_hash, $module) = &input_from_known_server($next_msg, $heap->{'remote_ip'}, $session_id);
1029     }
1030     # msg is from a gosa-si-client
1031     if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1032         ($msg, $msg_hash, $module) = &input_from_known_client($next_msg, $heap->{'remote_ip'}, $session_id);
1033     }
1034     # an error occurred
1035     if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1036         # if an incoming msg could not be decrypted (maybe a wrong key), send client a ping. If the client
1037         # could not understand a msg from its server the client cause a re-registering process
1038         daemon_log("$session_id WARNING cannot understand incoming msg, send 'ping'-msg to all host with ip '".$heap->{remote_ip}.
1039                 "' to cause a re-registering of the client if necessary", 3);
1040         my $sql_statement = "SELECT * FROM $main::known_clients_tn WHERE (hostname LIKE '".$heap->{'remote_ip'}."%')";
1041         my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
1042         while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1043             my $host_name = $hit->{'hostname'};
1044             my $host_key = $hit->{'hostkey'};
1045             my $ping_msg = "<xml> <header>gosa_ping</header> <source>$server_address</source> <target>$host_name</target></xml>";
1046             my $error = &send_msg_to_target($ping_msg, $host_name, $host_key, "gosa_ping", $session_id);
1047             &update_jobdb_status_for_send_msgs($ping_msg, $error);
1048         }
1049         $error++;
1050     }
1053     my $header;
1054     my $target;
1055     my $source;
1056     my $done = 0;
1057     my $sql;
1058     my $res;
1060     # check whether this message should be processed here
1061     if ($error == 0) {
1062         $header = @{$msg_hash->{'header'}}[0];
1063         $target = @{$msg_hash->{'target'}}[0];
1064         $source = @{$msg_hash->{'source'}}[0];
1065                 my $not_found_in_known_clients_db = 0;
1066                 my $not_found_in_known_server_db = 0;
1067                 my $not_found_in_foreign_clients_db = 0;
1068         my $local_address;
1069         my $local_mac;
1070         my ($target_ip, $target_port) = split(':', $target);
1071         
1072         # Determine the local ip address if target is an ip address
1073                 if ($target =~ /^\d+\.\d+\.\d+\.\d+:\d+$/) {
1074                         $local_address = &get_local_ip_for_remote_ip($target_ip).":$server_port";
1075                 } else {
1076             $local_address = $server_address;
1077         }
1079         # Determine the local mac address if target is a mac address
1080         if ($target =~ /^([0-9a-f]{2}:[0-9a-f]{2}:[0-9a-f]{2}:[0-9a-f]{2}:[0-9a-f]{2}:[0-9a-f]{2})$/i) {
1081             my $loc_ip = &get_local_ip_for_remote_ip($heap->{'remote_ip'});
1082             my $network_interface= &get_interface_for_ip($loc_ip);
1083             $local_mac = &get_mac_for_interface($network_interface);
1084         } else {
1085             $local_mac = $server_mac_address;
1086         }
1088         # target and source is equal to GOSA -> process here
1089         if (not $done) {
1090             if ($target eq "GOSA" && $source eq "GOSA") {
1091                 $done = 1;                    
1092                 &daemon_log("$session_id DEBUG: target and source is 'GOSA' -> process here", 7);
1093             }
1094         }
1096         # target is own address without forward_to_gosa-tag -> process here
1097         if (not $done) {
1098             #if ((($target eq $local_address) || ($target eq $local_mac) ) && (not exists $msg_hash->{'forward_to_gosa'})) {
1099             if (($target eq $local_address) && (not exists $msg_hash->{'forward_to_gosa'})) {
1100                 $done = 1;
1101                 if ($source eq "GOSA") {
1102                     $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1103                 }
1104                 &daemon_log("$session_id DEBUG: target is own address without forward_to_gosa-tag -> process here", 7);
1105             }
1106         }
1108         # target is a client address in known_clients -> process here
1109                 if (not $done) {
1110                                 $sql = "SELECT * FROM $known_clients_tn WHERE (hostname='$target' OR macaddress LIKE '$target')"; 
1111                                 $res = $known_clients_db->select_dbentry($sql);
1112                                 if (keys(%$res) > 0) {
1113                                                 $done = 1; 
1114                                                 my $hostname = $res->{1}->{'hostname'};
1115                                                 $msg =~ s/<target>$target<\/target>/<target>$hostname<\/target>/;
1116                         my $local_address = &get_local_ip_for_remote_ip($target_ip).":$server_port";
1117                         if ($source eq "GOSA") {
1118                             $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1119                         }
1120                         &daemon_log("$session_id DEBUG: target is a client address in known_clients -> process here", 7);
1122                                 } else {
1123                                                 $not_found_in_known_clients_db = 1;
1124                                 }
1125                 }
1126         
1127         # target ist own address with forward_to_gosa-tag not pointing to myself -> process here
1128         if (not $done) {
1129             my $forward_to_gosa =  @{$msg_hash->{'forward_to_gosa'}}[0];
1130             my $gosa_at;
1131             my $gosa_session_id;
1132             if (($target eq $local_address) && (defined $forward_to_gosa)){
1133                 my ($gosa_at, $gosa_session_id) = split(/,/, $forward_to_gosa);
1134                 if ($gosa_at ne $local_address) {
1135                     $done = 1;
1136                     &daemon_log("$session_id DEBUG: target is own address with forward_to_gosa-tag not pointing to myself -> process here", 7); 
1137                 }
1138             }
1139         }
1141         # if message should be processed here -> add message to incoming_db
1142                 if ($done) {
1143                                 # if a job or a gosa message comes from a foreign server, fake module to GosaPackages
1144                                 # so gosa-si-server knows how to process this kind of messages
1145                                 if ($header =~ /^gosa_/ || $header =~ /^job_/) {
1146                                                 $module = "GosaPackages";
1147                                 }
1149                                 my $res = $incoming_db->add_dbentry( {table=>$incoming_tn,
1150                                                                 primkey=>[],
1151                                                                 headertag=>$header,
1152                                                                 targettag=>$target,
1153                                                                 xmlmessage=>&encode_base64($msg),
1154                                                                 timestamp=>&get_time,
1155                                                                 module=>$module,
1156                                                                 sessionid=>$session_id,
1157                                                                 } );
1158                 }
1160         # target is own address with forward_to_gosa-tag pointing at myself -> forward to gosa
1161         if (not $done) {
1162             my $forward_to_gosa =  @{$msg_hash->{'forward_to_gosa'}}[0];
1163             my $gosa_at;
1164             my $gosa_session_id;
1165             if (($target eq $local_address) && (defined $forward_to_gosa)){
1166                 my ($gosa_at, $gosa_session_id) = split(/,/, $forward_to_gosa);
1167                 if ($gosa_at eq $local_address) {
1168                     my $session_reference = $kernel->ID_id_to_session($gosa_session_id);
1169                     if( defined $session_reference ) {
1170                         $heap = $session_reference->get_heap();
1171                     }
1172                     if(exists $heap->{'client'}) {
1173                         $msg = &encrypt_msg($msg, $GosaPackages_key);
1174                         $heap->{'client'}->put($msg);
1175                         &daemon_log("$session_id INFO: incoming '$header' message forwarded to GOsa", 5); 
1176                     }
1177                     $done = 1;
1178                     &daemon_log("$session_id DEBUG: target is own address with forward_to_gosa-tag pointing at myself -> forward to gosa", 7);
1179                 }
1180             }
1182         }
1184         # target is a client address in foreign_clients -> forward to registration server
1185         if (not $done) {
1186             $sql = "SELECT * FROM $foreign_clients_tn WHERE (hostname='$target' OR macaddress LIKE '$target')";
1187             $res = $foreign_clients_db->select_dbentry($sql);
1188             if (keys(%$res) > 0) {
1189                     my $hostname = $res->{1}->{'hostname'};
1190                     my ($host_ip, $host_port) = split(/:/, $hostname);
1191                     my $local_address =  &get_local_ip_for_remote_ip($host_ip).":$server_port";
1192                 my $regserver = $res->{1}->{'regserver'};
1193                 my $sql = "SELECT * FROM $known_server_tn WHERE hostname='$regserver'"; 
1194                 my $res = $known_server_db->select_dbentry($sql);
1195                 if (keys(%$res) > 0) {
1196                     my $regserver_key = $res->{1}->{'hostkey'};
1197                     $msg =~ s/<source>GOSA<\/source>/<source>$local_address<\/source>/;
1198                     $msg =~ s/<target>$target<\/target>/<target>$hostname<\/target>/;
1199                     if ($source eq "GOSA") {
1200                         $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1201                     }
1202                     &send_msg_to_target($msg, $regserver, $regserver_key, $header, $session_id);
1203                 }
1204                 $done = 1;
1205                 &daemon_log("$session_id DEBUG: target is a client address in foreign_clients -> forward to registration server", 7);
1206             } else {
1207                                 $not_found_in_foreign_clients_db = 1;
1208                         }
1209         }
1211         # target is a server address -> forward to server
1212         if (not $done) {
1213             $sql = "SELECT * FROM $known_server_tn WHERE (hostname='$target' OR macaddress LIKE '$target')";
1214             $res = $known_server_db->select_dbentry($sql);
1215             if (keys(%$res) > 0) {
1216                 my $hostkey = $res->{1}->{'hostkey'};
1218                 if ($source eq "GOSA") {
1219                     $msg =~ s/<source>GOSA<\/source>/<source>$local_address<\/source>/;
1220                     $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1222                 }
1224                 &send_msg_to_target($msg, $target, $hostkey, $header, $session_id);
1225                 $done = 1;
1226                 &daemon_log("$session_id DEBUG: target is a server address -> forward to server", 7);
1227             } else {
1228                                 $not_found_in_known_server_db = 1;
1229                         }
1230         }
1232                 
1233                 # target is not in foreign_clients_db, known_server_db or known_clients_db, maybe it is a complete new one -> process here
1234                 if ( $not_found_in_foreign_clients_db 
1235                                                 && $not_found_in_known_server_db
1236                                                 && $not_found_in_known_clients_db) {
1237                                 my $res = $incoming_db->add_dbentry( {table=>$incoming_tn,
1238                                                                 primkey=>[],
1239                                                                 headertag=>$header,
1240                                                                 targettag=>$target,
1241                                                                 xmlmessage=>&encode_base64($msg),
1242                                                                 timestamp=>&get_time,
1243                                                                 module=>$module,
1244                                                                 sessionid=>$session_id,
1245                                                                 } );
1246                                 $done = 1;
1247                 &daemon_log("$session_id DEBUG: target is not in foreign_clients_db, known_server_db or known_clients_db, maybe it is a complete new one -> process here", 7);
1248                 }
1251         if (not $done) {
1252             daemon_log("$session_id ERROR: do not know what to do with this message: $msg", 1);
1253             if ($source eq "GOSA") {
1254                 my %data = ('error_msg' => &encode_base64($msg), 'error_string' => "Do not know what to do with this message!");
1255                 my $error_msg = &build_msg("error", $local_address, "GOSA", \%data ); 
1257                 my $session_reference = $kernel->ID_id_to_session($session_id);
1258                 if( defined $session_reference ) {
1259                     $heap = $session_reference->get_heap();
1260                 }
1261                 if(exists $heap->{'client'}) {
1262                     $error_msg = &encrypt_msg($error_msg, $GosaPackages_key);
1263                     $heap->{'client'}->put($error_msg);
1264                 }
1265             }
1266         }
1268     }
1270     return;
1274 sub next_task {
1275     my ($session, $heap, $task) = @_[SESSION, HEAP, ARG0];
1276     my $running_task = POE::Wheel::Run->new(
1277             Program => sub { process_task($session, $heap, $task) },
1278             StdioFilter => POE::Filter::Reference->new(),
1279             StdoutEvent  => "task_result",
1280             StderrEvent  => "task_debug",
1281             CloseEvent   => "task_done",
1282             );
1283     $heap->{task}->{ $running_task->ID } = $running_task;
1286 sub handle_task_result {
1287     my ($kernel, $heap, $result) = @_[KERNEL, HEAP, ARG0];
1288     my $client_answer = $result->{'answer'};
1289     if( $client_answer =~ s/session_id=(\d+)$// ) {
1290         my $session_id = $1;
1291         if( defined $session_id ) {
1292             my $session_reference = $kernel->ID_id_to_session($session_id);
1293             if( defined $session_reference ) {
1294                 $heap = $session_reference->get_heap();
1295             }
1296         }
1298         if(exists $heap->{'client'}) {
1299             $heap->{'client'}->put($client_answer);
1300         }
1301     }
1302     $kernel->sig(CHLD => "child_reap");
1305 sub handle_task_debug {
1306     my $result = $_[ARG0];
1307     print STDERR "$result\n";
1310 sub handle_task_done {
1311     my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
1312     delete $heap->{task}->{$task_id};
1315 sub process_task {
1316     no strict "refs";
1317     #CHECK: Not @_[...]?
1318     my ($session, $heap, $task) = @_;
1319     my $error = 0;
1320     my $answer_l;
1321     my ($answer_header, @answer_target_l, $answer_source);
1322     my $client_answer = "";
1324     # prepare all variables needed to process message
1325     #my $msg = $task->{'xmlmessage'};
1326     my $msg = &decode_base64($task->{'xmlmessage'});
1327     my $incoming_id = $task->{'id'};
1328     my $module = $task->{'module'};
1329     my $header =  $task->{'headertag'};
1330     my $session_id = $task->{'sessionid'};
1331     my $msg_hash = $xml->XMLin($msg, ForceArray=>1);
1332     my $source = @{$msg_hash->{'source'}}[0];
1333     
1334     # set timestamp of incoming client uptodate, so client will not 
1335     # be deleted from known_clients because of expiration
1336     my $act_time = &get_time();
1337     my $sql = "UPDATE $known_clients_tn SET timestamp='$act_time' WHERE hostname='$source'"; 
1338     my $res = $known_clients_db->exec_statement($sql);
1340     ######################
1341     # process incoming msg
1342     if( $error == 0) {
1343         daemon_log("$session_id INFO: Incoming msg (session_id=$session_id) with header '".@{$msg_hash->{'header'}}[0]."'", 5); 
1344         daemon_log("$session_id DEBUG: Processing module ".$module, 7);
1345         $answer_l = &{ $module."::process_incoming_msg" }($msg, $msg_hash, $session_id);
1347         if ( 0 < @{$answer_l} ) {
1348             my $answer_str = join("\n", @{$answer_l});
1349             while ($answer_str =~ /<header>(\w+)<\/header>/g) {
1350                 daemon_log("$session_id INFO: got answer message with header '$1'", 5);
1351             }
1352             daemon_log("$session_id DEBUG: $module: got answer from module: \n".$answer_str,9);
1353         } else {
1354             daemon_log("$session_id DEBUG: $module: got no answer from module!" ,7);
1355         }
1357     }
1358     if( !$answer_l ) { $error++ };
1360     ########
1361     # answer
1362     if( $error == 0 ) {
1364         foreach my $answer ( @{$answer_l} ) {
1365             # check outgoing msg to xml validity
1366             my $answer_hash = &check_outgoing_xml_validity($answer, $session_id);
1367             if( not defined $answer_hash ) { next; }
1368             
1369             $answer_header = @{$answer_hash->{'header'}}[0];
1370             @answer_target_l = @{$answer_hash->{'target'}};
1371             $answer_source = @{$answer_hash->{'source'}}[0];
1373             # deliver msg to all targets 
1374             foreach my $answer_target ( @answer_target_l ) {
1376                 # targets of msg are all gosa-si-clients in known_clients_db
1377                 if( $answer_target eq "*" ) {
1378                     # answer is for all clients
1379                     my $sql_statement= "SELECT * FROM known_clients";
1380                     my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
1381                     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1382                         my $host_name = $hit->{hostname};
1383                         my $host_key = $hit->{hostkey};
1384                         my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1385                         &update_jobdb_status_for_send_msgs($answer, $error);
1386                     }
1387                 }
1389                 # targets of msg are all gosa-si-server in known_server_db
1390                 elsif( $answer_target eq "KNOWN_SERVER" ) {
1391                     # answer is for all server in known_server
1392                     my $sql_statement= "SELECT * FROM $known_server_tn";
1393                     my $query_res = $known_server_db->select_dbentry( $sql_statement ); 
1394                     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1395                         my $host_name = $hit->{hostname};
1396                         my $host_key = $hit->{hostkey};
1397                         $answer =~ s/<target>\S+<\/target>/<target>$host_name<\/target>/g;
1398                         my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1399                         &update_jobdb_status_for_send_msgs($answer, $error);
1400                     }
1401                 }
1403                 # target of msg is GOsa
1404                                 elsif( $answer_target eq "GOSA" ) {
1405                                         my $session_id = ($1) if $answer =~ /<session_id>(\d+?)<\/session_id>/;
1406                                         my $add_on = "";
1407                     if( defined $session_id ) {
1408                         $add_on = ".session_id=$session_id";
1409                     }
1410                     # answer is for GOSA and has to returned to connected client
1411                     my $gosa_answer = &encrypt_msg($answer, $GosaPackages_key);
1412                     $client_answer = $gosa_answer.$add_on;
1413                 }
1415                 # target of msg is job queue at this host
1416                 elsif( $answer_target eq "JOBDB") {
1417                     $answer =~ /<header>(\S+)<\/header>/;   
1418                     my $header;
1419                     if( defined $1 ) { $header = $1; }
1420                     my $error = &send_msg_to_target($answer, $server_address, $GosaPackages_key, $header, $session_id);
1421                     &update_jobdb_status_for_send_msgs($answer, $error);
1422                 }
1424                 # Target of msg is a mac address
1425                 elsif( $answer_target =~ /^([0-9a-f]{2}:[0-9a-f]{2}:[0-9a-f]{2}:[0-9a-f]{2}:[0-9a-f]{2}:[0-9a-f]{2})$/i ) {
1426                     daemon_log("$session_id INFO: target is mac address '$answer_target', looking for host in known_clients and foreign_clients", 5);
1427                     my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$answer_target'";
1428                     my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1429                     my $found_ip_flag = 0;
1430                     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1431                         my $host_name = $hit->{hostname};
1432                         my $host_key = $hit->{hostkey};
1433                         $answer =~ s/$answer_target/$host_name/g;
1434                         daemon_log("$session_id INFO: found host '$host_name', associated to '$answer_target'", 5);
1435                         my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1436                         &update_jobdb_status_for_send_msgs($answer, $error);
1437                         $found_ip_flag++ ;
1438                     }   
1439                     if ($found_ip_flag == 0) {
1440                         my $sql = "SELECT * FROM $foreign_clients_tn WHERE macaddress LIKE '$answer_target'";
1441                         my $res = $foreign_clients_db->select_dbentry($sql);
1442                         while( my ($hit_num, $hit) = each %{ $res } ) {
1443                             my $host_name = $hit->{hostname};
1444                             my $reg_server = $hit->{regserver};
1445                             daemon_log("$session_id INFO: found host '$host_name' with mac '$answer_target', registered at '$reg_server'", 5);
1446                             
1447                             # Fetch key for reg_server
1448                             my $reg_server_key;
1449                             my $sql = "SELECT * FROM $known_server_tn WHERE hostname='$reg_server'";
1450                             my $res = $known_server_db->select_dbentry($sql);
1451                             if (exists $res->{1}) {
1452                                 $reg_server_key = $res->{1}->{'hostkey'}; 
1453                             } else {
1454                                 daemon_log("$session_id ERROR: cannot find hostkey for '$host_name' in '$known_server_tn'", 1); 
1455                                 daemon_log("$session_id ERROR: unable to forward answer to correct registration server, processing is aborted!", 1); 
1456                                 $reg_server_key = undef;
1457                             }
1459                             # Send answer to server where client is registered
1460                             if (defined $reg_server_key) {
1461                                 $answer =~ s/$answer_target/$host_name/g;
1462                                 my $error = &send_msg_to_target($answer, $reg_server, $reg_server_key, $answer_header, $session_id);
1463                                 &update_jobdb_status_for_send_msgs($answer, $error);
1464                                 $found_ip_flag++ ;
1465                             }
1466                         }
1467                     }
1468                     if( $found_ip_flag == 0) {
1469                         daemon_log("$session_id WARNING: no host found in known_clients with mac address '$answer_target'", 3);
1470                     }
1472                 # Answer is for one specific host   
1473                 } else {
1474                     # get encrypt_key
1475                     my $encrypt_key = &get_encrypt_key($answer_target);
1476                     if( not defined $encrypt_key ) {
1477                         # unknown target
1478                         daemon_log("$session_id WARNING: unknown target '$answer_target'", 3);
1479                         next;
1480                     }
1481                     my $error = &send_msg_to_target($answer, $answer_target, $encrypt_key, $answer_header,$session_id);
1482                     &update_jobdb_status_for_send_msgs($answer, $error);
1483                 }
1484             }
1485         }
1486     }
1488     my $filter = POE::Filter::Reference->new();
1489     my %result = ( 
1490             status => "seems ok to me",
1491             answer => $client_answer,
1492             );
1494     my $output = $filter->put( [ \%result ] );
1495     print @$output;
1500 sub session_start {
1501     my ($kernel) = $_[KERNEL];
1502     $global_kernel = $kernel;
1503     $kernel->yield('register_at_foreign_servers');
1504         $kernel->yield('create_fai_server_db', $fai_server_tn );
1505         $kernel->yield('create_fai_release_db', $fai_release_tn );
1506     $kernel->yield('watch_for_next_tasks');
1507         $kernel->sig(USR1 => "sig_handler");
1508         $kernel->sig(USR2 => "recreate_packages_db");
1509         $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1510         $kernel->delay_set('watch_for_done_jobs', $job_queue_loop_delay); 
1511     $kernel->delay_set('watch_for_modified_jobs', $modified_jobs_loop_delay); 
1512         $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay);
1513     $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay);
1514         $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay);
1515     $kernel->delay_set('watch_for_old_known_clients', $job_queue_loop_delay);
1517     # Start opsi check
1518     if ($opsi_enabled eq "true") {
1519         $kernel->delay_set('watch_for_opsi_jobs', $job_queue_opsi_delay); 
1520     }
1525 sub watch_for_done_jobs {
1526     #CHECK: $heap for what?
1527     my ($kernel,$heap) = @_[KERNEL, HEAP];
1529     my $sql_statement = "SELECT * FROM ".$job_queue_tn." WHERE ((status='done') AND (modified='0'))";
1530         my $res = $job_db->select_dbentry( $sql_statement );
1532     while( my ($id, $hit) = each %{$res} ) {
1533         my $jobdb_id = $hit->{id};
1534         my $sql_statement = "DELETE FROM $job_queue_tn WHERE id=$jobdb_id"; 
1535         my $res = $job_db->del_dbentry($sql_statement); 
1536     }
1538     $kernel->delay_set('watch_for_done_jobs',$job_queue_loop_delay);
1542 sub watch_for_opsi_jobs {
1543     my ($kernel) = $_[KERNEL];
1545     # This is not very nice to look for opsi install jobs, but headertag has to be trigger_action_reinstall. The only way to identify a 
1546     # opsi install job is to parse the xml message. There is still the correct header.
1547     my $sql_statement = "SELECT * FROM ".$job_queue_tn." WHERE ((xmlmessage LIKE '%opsi_install_client</header>%') AND (status='processing') AND (siserver='localhost'))";
1548         my $res = $job_db->select_dbentry( $sql_statement );
1550     # Ask OPSI for an update of the running jobs
1551     while (my ($id, $hit) = each %$res ) {
1552         # Determine current parameters of the job
1553         my $hostId = $hit->{'plainname'};
1554         my $macaddress = $hit->{'macaddress'};
1555         my $progress = $hit->{'progress'};
1557         my $result= {};
1558         
1559         # For hosts, only return the products that are or get installed
1560         my $callobj;
1561         $callobj = {
1562             method  => 'getProductStates_hash',
1563             params  => [ $hostId ],
1564             id  => 1,
1565         };
1566         
1567         my $hres = $opsi_client->call($opsi_url, $callobj);
1568         #my ($hres_err, $hres_err_string) = &check_opsi_res($hres);
1569         if (not &check_opsi_res($hres)) {
1570             my $htmp= $hres->result->{$hostId};
1571         
1572             # Check state != not_installed or action == setup -> load and add
1573             my $products= 0;
1574             my $installed= 0;
1575             my $installing = 0;
1576             my $error= 0;  
1577             my @installed_list;
1578             my @error_list;
1579             my $act_status = "none";
1580             foreach my $product (@{$htmp}){
1582                 if ($product->{'installationStatus'} ne "not_installed" or
1583                         $product->{'actionRequest'} eq "setup"){
1585                     # Increase number of products for this host
1586                     $products++;
1587         
1588                     if ($product->{'installationStatus'} eq "failed"){
1589                         $result->{$product->{'productId'}}= "error";
1590                         unshift(@error_list, $product->{'productId'});
1591                         $error++;
1592                     }
1593                     if ($product->{'installationStatus'} eq "installed" && $product->{'actionRequest'}  eq "none"){
1594                         $result->{$product->{'productId'}}= "installed";
1595                         unshift(@installed_list, $product->{'productId'});
1596                         $installed++;
1597                     }
1598                     if ($product->{'installationStatus'} eq "installing"){
1599                         $result->{$product->{'productId'}}= "installing";
1600                         $installing++;
1601                         $act_status = "installing - ".$product->{'productId'};
1602                     }
1603                 }
1604             }
1605         
1606             # Estimate "rough" progress, avoid division by zero
1607             if ($products == 0) {
1608                 $result->{'progress'}= 0;
1609             } else {
1610                 $result->{'progress'}= int($installed * 100 / $products);
1611             }
1613             # Set updates in job queue
1614             if ((not $error) && (not $installing) && ($installed)) {
1615                 $act_status = "installed - ".join(", ", @installed_list);
1616             }
1617             if ($error) {
1618                 $act_status = "error - ".join(", ", @error_list);
1619             }
1620             if ($progress ne $result->{'progress'} ) {
1621                 # Updating progress and result 
1622                 my $update_statement = "UPDATE $job_queue_tn SET modified='1', progress='".$result->{'progress'}."', result='$act_status' WHERE macaddress='$macaddress' AND siserver='localhost'";
1623                 my $update_res = $job_db->update_dbentry($update_statement);
1624             }
1625             if ($progress eq 100) { 
1626                 # Updateing status
1627                 my $done_statement = "UPDATE $job_queue_tn SET modified='1', ";
1628                 if ($error) {
1629                     $done_statement .= "status='error'";
1630                 } else {
1631                     $done_statement .= "status='done'";
1632                 }
1633                 $done_statement .= " WHERE macaddress='$macaddress' AND siserver='localhost'";
1634                 my $done_res = $job_db->update_dbentry($done_statement);
1635             }
1638         }
1639     }
1641     $kernel->delay_set('watch_for_opsi_jobs', $job_queue_opsi_delay);
1645 # If a job got an update or was modified anyway, send to all other si-server an update message of this jobs.
1646 sub watch_for_modified_jobs {
1647     my ($kernel,$heap) = @_[KERNEL, HEAP];
1649     my $sql_statement = "SELECT * FROM $job_queue_tn WHERE ((siserver='localhost') AND (modified='1'))"; 
1650     my $res = $job_db->select_dbentry( $sql_statement );
1651     
1652     # if db contains no jobs which should be update, do nothing
1653     if (keys %$res != 0) {
1655         if ($job_synchronization  eq "true") {
1656             # make out of the db result a gosa-si message   
1657             my $update_msg = &db_res2si_msg ($res, "foreign_job_updates", "KNOWN_SERVER", "MY_LOCAL_ADDRESS");
1658  
1659             # update all other SI-server
1660             &inform_all_other_si_server($update_msg);
1661         }
1663         # set jobs all jobs to modified = 0, wait until the next modification for updates of other si-server
1664         $sql_statement = "UPDATE $job_queue_tn SET modified='0' ";
1665         $res = $job_db->update_dbentry($sql_statement);
1666     }
1668     $kernel->delay_set('watch_for_modified_jobs', $modified_jobs_loop_delay);
1672 sub watch_for_new_jobs {
1673         if($watch_for_new_jobs_in_progress == 0) {
1674                 $watch_for_new_jobs_in_progress = 1;
1675                 my ($kernel,$heap) = @_[KERNEL, HEAP];
1677                 # check gosa job quaeue for jobs with executable timestamp
1678                 my $timestamp = &get_time();
1679                 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE status='waiting' AND (CAST (timestamp AS INTEGER)) < $timestamp ORDER BY timestamp";
1680                 my $res = $job_db->exec_statement( $sql_statement );
1682                 # Merge all new jobs that would do the same actions
1683                 my @drops;
1684                 my $hits;
1685                 foreach my $hit (reverse @{$res} ) {
1686                         my $macaddress= lc @{$hit}[8];
1687                         my $headertag= @{$hit}[5];
1688                         if(
1689                                 defined($hits->{$macaddress}) &&
1690                                 defined($hits->{$macaddress}->{$headertag}) &&
1691                                 defined($hits->{$macaddress}->{$headertag}[0])
1692                         ) {
1693                                 push @drops, "DELETE FROM $job_queue_tn WHERE id = $hits->{$macaddress}->{$headertag}[0]";
1694                         }
1695                         $hits->{$macaddress}->{$headertag}= $hit;
1696                 }
1698                 # Delete new jobs with a matching job in state 'processing'
1699                 foreach my $macaddress (keys %{$hits}) {
1700                         foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1701                                 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1702                                 if(defined($jobdb_id)) {
1703                                         my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND headertag='$jobdb_headertag' AND status='processing'";
1704                                         my $res = $job_db->exec_statement( $sql_statement );
1705                                         foreach my $hit (@{$res}) {
1706                                                 push @drops, "DELETE FROM $job_queue_tn WHERE id=$jobdb_id";
1707                                         }
1708                                 } else {
1709                                         daemon_log("J ERROR: Job without id exists for macaddress $macaddress!", 1);
1710                                 }
1711                         }
1712                 }
1714                 # Commit deletion
1715                 $job_db->exec_statementlist(\@drops);
1717                 # Look for new jobs that could be executed
1718                 foreach my $macaddress (keys %{$hits}) {
1720                         # Look if there is an executing job
1721                         my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND status='processing'";
1722                         my $res = $job_db->exec_statement( $sql_statement );
1724                         # Skip new jobs for host if there is a processing job
1725                         if(defined($res) and defined @{$res}[0]) {
1726                                 # Prevent race condition if there is a trigger_activate job waiting and a goto-activation job processing
1727                                 my $row = @{$res}[0] if (ref $res eq 'ARRAY');
1728                                 if(@{$row}[5] eq 'trigger_action_reinstall') {
1729                                         my $sql_statement_2 =  "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND status='waiting' AND headertag = 'trigger_activate_new'"; 
1730                                         my $res_2 = $job_db->exec_statement( $sql_statement_2 );
1731                                         if(defined($res_2) and defined @{$res_2}[0]) {
1732                                                 # Set status from goto-activation to 'waiting' and update timestamp
1733                                                 $job_db->exec_statement("UPDATE $job_queue_tn SET status='waiting' WHERE macaddress LIKE '$macaddress' AND headertag = 'trigger_action_reinstall'");
1734                                                 $job_db->exec_statement("UPDATE $job_queue_tn SET timestamp='".&get_time(30)."' WHERE macaddress LIKE '$macaddress' AND headertag = 'trigger_action_reinstall'");
1735                                         }
1736                                 }
1737                                 next;
1738                         }
1740                         foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1741                                 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1742                                 if(defined($jobdb_id)) {
1743                                         my $job_msg = @{$hits->{$macaddress}->{$jobdb_headertag}}[7];
1745                                         daemon_log("J DEBUG: its time to execute $job_msg", 7);
1746                                         my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$macaddress'";
1747                                         my $res_hash = $known_clients_db->select_dbentry( $sql_statement );
1749                                         # expect macaddress is unique!!!!!!
1750                                         my $target = $res_hash->{1}->{hostname};
1752                                         # change header
1753                                         $job_msg =~ s/<header>job_/<header>gosa_/;
1755                                         # add sqlite_id
1756                                         $job_msg =~ s/<\/xml>$/<jobdb_id>$jobdb_id<\/jobdb_id><\/xml>/;
1758                                         $job_msg =~ /<header>(\S+)<\/header>/;
1759                                         my $header = $1 ;
1760                                         my $func_error = &send_msg_to_target($job_msg, $server_address, $GosaPackages_key, $header, "J");
1762                                         # update status in job queue to 'processing'
1763                                         $sql_statement = "UPDATE $job_queue_tn SET status='processing' WHERE id=$jobdb_id";
1764                                         my $res = $job_db->update_dbentry($sql_statement);
1765 # TODO: abfangen ob alles in ordnung ist oder nicht, wenn nicht error schmeißen                                        
1767                                         # We don't want parallel processing
1768                                         last;
1769                                 }
1770                         }
1771                 }
1773                 $watch_for_new_jobs_in_progress = 0;
1774                 $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1775         }
1779 sub watch_for_new_messages {
1780     my ($kernel,$heap) = @_[KERNEL, HEAP];
1781     my @coll_user_msg;   # collection list of outgoing messages
1782     
1783     # check messaging_db for new incoming messages with executable timestamp
1784     my $timestamp = &get_time();
1785     my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( (CAST(timestamp AS INTEGER))<$timestamp AND flag='n' AND direction='in' )";
1786     my $res = $messaging_db->exec_statement( $sql_statement );
1787         foreach my $hit (@{$res}) {
1789         # create outgoing messages
1790         my $message_to = @{$hit}[3];
1791         # translate message_to to plain login name
1792         my @message_to_l = split(/,/, $message_to);  
1793                 my %receiver_h; 
1794                 foreach my $receiver (@message_to_l) {
1795                         if ($receiver =~ /^u_([\s\S]*)$/) {
1796                                 $receiver_h{$1} = 0;
1797                         } elsif ($receiver =~ /^g_([\s\S]*)$/) {
1798                                 my $group_name = $1;
1799                                 # fetch all group members from ldap and add them to receiver hash
1800                                 my $ldap_handle = &get_ldap_handle();
1801                                 if (defined $ldap_handle) {
1802                                                 my $mesg = $ldap_handle->search(
1803                                                                                 base => $ldap_base,
1804                                                                                 scope => 'sub',
1805                                                                                 attrs => ['memberUid'],
1806                                                                                 filter => "cn=$group_name",
1807                                                                                 );
1808                                                 if ($mesg->count) {
1809                                                                 my @entries = $mesg->entries;
1810                                                                 foreach my $entry (@entries) {
1811                                                                                 my @receivers= $entry->get_value("memberUid");
1812                                                                                 foreach my $receiver (@receivers) { 
1813                                                                                                 $receiver_h{$1} = 0;
1814                                                                                 }
1815                                                                 }
1816                                                 } 
1817                                                 # translating errors ?
1818                                                 if ($mesg->code) {
1819                                                                 daemon_log("M ERROR: unable to translate group '$group_name' to user list for message delivery: $mesg->error", 1);
1820                                                 }
1821                                 # ldap handle error ?           
1822                                 } else {
1823                                         daemon_log("M ERROR: unable to translate group '$group_name' to user list for message delivery: no ldap handle available", 1);
1824                                 }
1825                         } else {
1826                                 my $sbjct = &encode_base64(@{$hit}[1]);
1827                                 my $msg = &encode_base64(@{$hit}[7]);
1828                                 &daemon_log("M WARNING: unknown receiver '$receiver' for a user-message '$sbjct - $msg'", 3); 
1829                         }
1830                 }
1831                 my @receiver_l = keys(%receiver_h);
1833         my $message_id = @{$hit}[0];
1835         #add each outgoing msg to messaging_db
1836         my $receiver;
1837         foreach $receiver (@receiver_l) {
1838             my $sql_statement = "INSERT INTO $messaging_tn (id, subject, message_from, message_to, flag, direction, delivery_time, message, timestamp) ".
1839                 "VALUES ('".
1840                 $message_id."', '".    # id
1841                 @{$hit}[1]."', '".     # subject
1842                 @{$hit}[2]."', '".     # message_from
1843                 $receiver."', '".      # message_to
1844                 "none"."', '".         # flag
1845                 "out"."', '".          # direction
1846                 @{$hit}[6]."', '".     # delivery_time
1847                 @{$hit}[7]."', '".     # message
1848                 $timestamp."'".     # timestamp
1849                 ")";
1850             &daemon_log("M DEBUG: $sql_statement", 1);
1851             my $res = $messaging_db->exec_statement($sql_statement);
1852             &daemon_log("M INFO: message '".@{$hit}[0]."' is prepared for delivery to receiver '$receiver'", 5);
1853         }
1855         # set incoming message to flag d=deliverd
1856         $sql_statement = "UPDATE $messaging_tn SET flag='p' WHERE id='$message_id'"; 
1857         &daemon_log("M DEBUG: $sql_statement", 7);
1858         $res = $messaging_db->update_dbentry($sql_statement);
1859         &daemon_log("M INFO: message '$message_id' is set to flag 'p' (processed)", 5);
1860     }
1862     $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay); 
1863     return;
1866 sub watch_for_delivery_messages {
1867     my ($kernel, $heap) = @_[KERNEL, HEAP];
1869     # select outgoing messages
1870     my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( flag='p' AND direction='out' )";
1871     #&daemon_log("0 DEBUG: $sql", 7);
1872     my $res = $messaging_db->exec_statement( $sql_statement );
1873     
1874     # build out msg for each    usr
1875     foreach my $hit (@{$res}) {
1876         my $receiver = @{$hit}[3];
1877         my $msg_id = @{$hit}[0];
1878         my $subject = @{$hit}[1];
1879         my $message = @{$hit}[7];
1881         # resolve usr -> host where usr is logged in
1882         my $sql = "SELECT * FROM $login_users_tn WHERE (user='$receiver')"; 
1883         #&daemon_log("0 DEBUG: $sql", 7);
1884         my $res = $login_users_db->exec_statement($sql);
1886         # reciver is logged in nowhere
1887         if (not ref(@$res[0]) eq "ARRAY") { next; }    
1889                 my $send_succeed = 0;
1890                 foreach my $hit (@$res) {
1891                                 my $receiver_host = @$hit[0];
1892                 my $delivered2host = 0;
1893                                 &daemon_log("M DEBUG: user '$receiver' is logged in at host '$receiver_host'", 7);
1895                                 # Looking for host in know_clients_db 
1896                                 my $sql = "SELECT * FROM $known_clients_tn WHERE (hostname='$receiver_host')";
1897                                 my $res = $known_clients_db->exec_statement($sql);
1899                 # Host is known in known_clients_db
1900                 if (ref(@$res[0]) eq "ARRAY") {
1901                     my $receiver_key = @{@{$res}[0]}[2];
1902                     my %data = ('subject' => $subject, 'message' => $message, 'usr' => $receiver);
1903                     my $out_msg = &build_msg("usr_msg", $server_address, $receiver_host, \%data ); 
1904                     my $error = &send_msg_to_target($out_msg, $receiver_host, $receiver_key, "usr_msg", 0); 
1905                     if ($error == 0 ) {
1906                         $send_succeed++ ;
1907                         $delivered2host++ ;
1908                         &daemon_log("M DEBUG: send message for user '$receiver' to host '$receiver_host'", 7); 
1909                     } else {
1910                         &daemon_log("M DEBUG: cannot send message for user '$receiver' to host '$receiver_host'", 7); 
1911                     }
1912                 }
1913                 
1914                 # Message already send, do not need to do anything more, otherwise ...
1915                 if ($delivered2host) { next;}
1916     
1917                 # ...looking for host in foreign_clients_db
1918                 $sql = "SELECT * FROM $foreign_clients_tn WHERE (hostname='$receiver_host')";
1919                 $res = $foreign_clients_db->exec_statement($sql);
1920   
1921                                 # Host is known in foreign_clients_db 
1922                                 if (ref(@$res[0]) eq "ARRAY") { 
1923                     my $registration_server = @{@{$res}[0]}[2];
1924                     
1925                     # Fetch encryption key for registration server
1926                     my $sql = "SELECT * FROM $known_server_tn WHERE (hostname='$registration_server')";
1927                     my $res = $known_server_db->exec_statement($sql);
1928                     if (ref(@$res[0]) eq "ARRAY") { 
1929                         my $registration_server_key = @{@{$res}[0]}[3];
1930                         my %data = ('subject' => $subject, 'message' => $message, 'usr' => $receiver);
1931                         my $out_msg = &build_msg("usr_msg", $server_address, $receiver_host, \%data ); 
1932                         my $error = &send_msg_to_target($out_msg, $registration_server, $registration_server_key, "usr_msg", 0); 
1933                         if ($error == 0 ) {
1934                             $send_succeed++ ;
1935                             $delivered2host++ ;
1936                             &daemon_log("M DEBUG: send message for user '$receiver' to server '$registration_server'", 7); 
1937                         } else {
1938                             &daemon_log("M ERROR: cannot send message for user '$receiver' to server '$registration_server'", 1); 
1939                         }
1941                     } else {
1942                         &daemon_log("M ERROR: host '$receiver_host' is reported to be ".
1943                                 "registrated at server '$registration_server', ".
1944                                 "but no data available in known_server_db ", 1); 
1945                     }
1946                 }
1947                 
1948                 if (not $delivered2host) {
1949                     &daemon_log("M ERROR: unable to send user message to host '$receiver_host'", 1);
1950                 }
1951                 }
1953                 if ($send_succeed) {
1954                                 # set outgoing msg at db to deliverd
1955                                 my $sql = "UPDATE $messaging_tn SET flag='d' WHERE (id='$msg_id' AND direction='out' AND message_to='$receiver')"; 
1956                                 my $res = $messaging_db->exec_statement($sql); 
1957                 &daemon_log("M INFO: send message for user '$receiver' to logged in hosts", 5);
1958                 } else {
1959             &daemon_log("M WARNING: failed to deliver message for user '$receiver'", 3); 
1960         }
1961         }
1963     $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay); 
1964     return;
1968 sub watch_for_done_messages {
1969     my ($kernel,$heap) = @_[KERNEL, HEAP];
1971     my $sql = "SELECT * FROM $messaging_tn WHERE (flag='p' AND direction='in')"; 
1972     #&daemon_log("0 DEBUG: $sql", 7);
1973     my $res = $messaging_db->exec_statement($sql); 
1975     foreach my $hit (@{$res}) {
1976         my $msg_id = @{$hit}[0];
1978         my $sql = "SELECT * FROM $messaging_tn WHERE (id='$msg_id' AND direction='out' AND (NOT flag='s'))"; 
1979         #&daemon_log("0 DEBUG: $sql", 7); 
1980         my $res = $messaging_db->exec_statement($sql);
1982         # not all usr msgs have been seen till now
1983         if ( ref(@$res[0]) eq "ARRAY") { next; }
1984         
1985         $sql = "DELETE FROM $messaging_tn WHERE (id='$msg_id')"; 
1986         #&daemon_log("0 DEBUG: $sql", 7);
1987         $res = $messaging_db->exec_statement($sql);
1988     
1989     }
1991     $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay); 
1992     return;
1996 sub watch_for_old_known_clients {
1997     my ($kernel,$heap) = @_[KERNEL, HEAP];
1999     my $sql_statement = "SELECT * FROM $known_clients_tn";
2000     my $res = $known_clients_db->select_dbentry( $sql_statement );
2002     my $act_time = int(&get_time());
2004     while ( my ($hit_num, $hit) = each %$res) {
2005         my $expired_timestamp = int($hit->{'timestamp'});
2006         $expired_timestamp =~ /(\d{4})(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)/;
2007         my $dt = DateTime->new( year   => $1,
2008                 month  => $2,
2009                 day    => $3,
2010                 hour   => $4,
2011                 minute => $5,
2012                 second => $6,
2013                 );
2015         $dt->add( seconds => 2 * int($hit->{'keylifetime'}) );
2016         $expired_timestamp = $dt->ymd('').$dt->hms('');
2017         if ($act_time > $expired_timestamp) {
2018             my $hostname = $hit->{'hostname'};
2019             my $del_sql = "DELETE FROM $known_clients_tn WHERE hostname='$hostname'"; 
2020             my $del_res = $known_clients_db->exec_statement($del_sql);
2022             &main::daemon_log("0 INFO: timestamp '".$hit->{'timestamp'}."' of client '$hostname' is expired('$expired_timestamp'), client will be deleted from known_clients_db", 5);
2023         }
2025     }
2027     $kernel->delay_set('watch_for_old_known_clients', $job_queue_loop_delay);
2031 sub watch_for_next_tasks {
2032     my ($kernel,$heap) = @_[KERNEL, HEAP];
2034     my $sql = "SELECT * FROM $incoming_tn";
2035     my $res = $incoming_db->select_dbentry($sql);
2037     while ( my ($hit_num, $hit) = each %$res) {
2038         my $headertag = $hit->{'headertag'};
2039         if ($headertag =~ /^answer_(\d+)/) {
2040             # do not start processing, this message is for a still running POE::Wheel
2041             next;
2042         }
2043         my $message_id = $hit->{'id'};
2044         $kernel->yield('next_task', $hit);
2046         my $sql = "DELETE FROM $incoming_tn WHERE id=$message_id";
2047         my $res = $incoming_db->exec_statement($sql);
2048     }
2050     $kernel->delay_set('watch_for_next_tasks', 0.1); 
2054 sub get_ldap_handle {
2055         my ($session_id) = @_;
2056         my $heap;
2057         my $ldap_handle;
2059         if (not defined $session_id ) { $session_id = 0 };
2060         if ($session_id =~ /[^0-9]*/) { $session_id = 0 };
2062         if ($session_id == 0) {
2063                 daemon_log("$session_id DEBUG: get_ldap_handle invoked without a session_id, create a new ldap_handle", 7); 
2064                 $ldap_handle = Net::LDAP->new( $ldap_uri );
2065                 $ldap_handle->bind($ldap_admin_dn, password => $ldap_admin_password) or daemon_log("$session_id ERROR: Bind to LDAP $ldap_uri as $ldap_admin_dn failed!"); 
2067         } else {
2068                 my $session_reference = $global_kernel->ID_id_to_session($session_id);
2069                 if( defined $session_reference ) {
2070                         $heap = $session_reference->get_heap();
2071                 }
2073                 if (not defined $heap) {
2074                         daemon_log("$session_id DEBUG: cannot get heap for session_id '$session_id'", 7); 
2075                         return;
2076                 }
2078                 # TODO: This "if" is nonsense, because it doesn't prove that the
2079                 #       used handle is still valid - or if we've to reconnect...
2080                 #if (not exists $heap->{ldap_handle}) {
2081                         $ldap_handle = Net::LDAP->new( $ldap_uri );
2082                         $ldap_handle->bind($ldap_admin_dn, password => $ldap_admin_password) or daemon_log("$session_id ERROR: Bind to LDAP $ldap_uri as $ldap_admin_dn failed!"); 
2083                         $heap->{ldap_handle} = $ldap_handle;
2084                 #}
2085         }
2086         return $ldap_handle;
2090 sub change_fai_state {
2091     my ($st, $targets, $session_id) = @_;
2092     $session_id = 0 if not defined $session_id;
2093     # Set FAI state to localboot
2094     my %mapActions= (
2095         reboot    => '',
2096         update    => 'softupdate',
2097         localboot => 'localboot',
2098         reinstall => 'install',
2099         rescan    => '',
2100         wake      => '',
2101         memcheck  => 'memcheck',
2102         sysinfo   => 'sysinfo',
2103         install   => 'install',
2104     );
2106     # Return if this is unknown
2107     if (!exists $mapActions{ $st }){
2108         daemon_log("$session_id ERROR: unknown action '$st', can not translate ot FAIstate", 1); 
2109       return;
2110     }
2112     my $state= $mapActions{ $st };
2114     my $ldap_handle = &get_ldap_handle($session_id);
2115     if( defined($ldap_handle) ) {
2117       # Build search filter for hosts
2118         my $search= "(&(objectClass=GOhard)";
2119         foreach (@{$targets}){
2120             $search.= "(macAddress=$_)";
2121         }
2122         $search.= ")";
2124       # If there's any host inside of the search string, procress them
2125         if (!($search =~ /macAddress/)){
2126             daemon_log("$session_id ERROR: no macAddress found in filter statement for LDAP search: '$search'", 1);    
2127             return;
2128         }
2130       # Perform search for Unit Tag
2131       my $mesg = $ldap_handle->search(
2132           base   => $ldap_base,
2133           scope  => 'sub',
2134           attrs  => ['dn', 'FAIstate', 'objectClass'],
2135           filter => "$search"
2136           );
2138           if ($mesg->count) {
2139                   my @entries = $mesg->entries;
2140                   if (0 == @entries) {
2141                                   daemon_log("$session_id ERROR: ldap search failed: ldap_base=$ldap_base, filter=$search", 1); 
2142                   }
2144                   foreach my $entry (@entries) {
2145                           # Only modify entry if it is not set to '$state'
2146                           if ($entry->get_value("FAIstate") ne "$state"){
2147                                   daemon_log("$session_id INFO: Setting FAIstate to '$state' for ".$entry->dn, 5);
2148                                   my $result;
2149                                   my %tmp = map { $_ => 1 } $entry->get_value("objectClass");
2150                                   if (exists $tmp{'FAIobject'}){
2151                                           if ($state eq ''){
2152                                                   $result= $ldap_handle->modify($entry->dn, changes => [
2153                                                           delete => [ FAIstate => [] ] ]);
2154                                           } else {
2155                                                   $result= $ldap_handle->modify($entry->dn, changes => [
2156                                                           replace => [ FAIstate => $state ] ]);
2157                                           }
2158                                   } elsif ($state ne ''){
2159                                           $result= $ldap_handle->modify($entry->dn, changes => [
2160                                                   add     => [ objectClass => 'FAIobject' ],
2161                                                   add     => [ FAIstate => $state ] ]);
2162                                   }
2164                                   # Errors?
2165                                   if ($result->code){
2166                                           daemon_log("$session_id Error: Setting FAIstate to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
2167                                   }
2168                           } else {
2169                                   daemon_log("$session_id DEBUG FAIstate at host '".$entry->dn."' already at state '$st'", 7); 
2170                           }  
2171                   }
2172           } else {
2173                 daemon_log("$session_id ERROR: LDAP search failed: ldap_base=$ldap_base, filter=$search", 1);
2174           }
2176     # if no ldap handle defined
2177     } else {
2178         daemon_log("$session_id ERROR: no LDAP handle defined for update FAIstate", 1); 
2179     }
2181         return;
2185 sub change_goto_state {
2186     my ($st, $targets, $session_id) = @_;
2187     $session_id = 0  if not defined $session_id;
2189     # Switch on or off?
2190     my $state= $st eq 'active' ? 'active': 'locked';
2192     my $ldap_handle = &get_ldap_handle($session_id);
2193     if( defined($ldap_handle) ) {
2195       # Build search filter for hosts
2196       my $search= "(&(objectClass=GOhard)";
2197       foreach (@{$targets}){
2198         $search.= "(macAddress=$_)";
2199       }
2200       $search.= ")";
2202       # If there's any host inside of the search string, procress them
2203       if (!($search =~ /macAddress/)){
2204         return;
2205       }
2207       # Perform search for Unit Tag
2208       my $mesg = $ldap_handle->search(
2209           base   => $ldap_base,
2210           scope  => 'sub',
2211           attrs  => ['dn', 'gotoMode'],
2212           filter => "$search"
2213           );
2215       if ($mesg->count) {
2216         my @entries = $mesg->entries;
2217         foreach my $entry (@entries) {
2219           # Only modify entry if it is not set to '$state'
2220           if ($entry->get_value("gotoMode") ne $state){
2222             daemon_log("$session_id INFO: Setting gotoMode to '$state' for ".$entry->dn, 5);
2223             my $result;
2224             $result= $ldap_handle->modify($entry->dn, changes => [
2225                                                 replace => [ gotoMode => $state ] ]);
2227             # Errors?
2228             if ($result->code){
2229               &daemon_log("$session_id Error: Setting gotoMode to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
2230             }
2232           }
2233         }
2234       } else {
2235                 daemon_log("$session_id ERROR: LDAP search failed in function change_goto_state: ldap_base=$ldap_base, filter=$search", 1);
2236           }
2238     }
2242 sub run_recreate_packages_db {
2243     my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
2244     my $session_id = $session->ID;
2245         &main::daemon_log("$session_id INFO: Recreating FAI Packages DB ('$fai_release_tn', '$fai_server_tn', '$packages_list_tn')", 5);
2246         $kernel->yield('create_fai_release_db', $fai_release_tn);
2247         $kernel->yield('create_fai_server_db', $fai_server_tn);
2248         return;
2252 sub run_create_fai_server_db {
2253     my ($kernel, $session, $heap, $table_name) = @_[KERNEL, SESSION, HEAP, ARG0];
2254     my $session_id = $session->ID;
2255     my $task = POE::Wheel::Run->new(
2256             Program => sub { &create_fai_server_db($table_name,$kernel, undef, $session_id) },
2257             StdoutEvent  => "session_run_result",
2258             StderrEvent  => "session_run_debug",
2259             CloseEvent   => "session_run_done",
2260             );
2262     $heap->{task}->{ $task->ID } = $task;
2263     return;
2267 sub create_fai_server_db {
2268     my ($table_name, $kernel, $dont_create_packages_list, $session_id) = @_;
2269         my $result;
2271         if (not defined $session_id) { $session_id = 0; }
2272     my $ldap_handle = &get_ldap_handle();
2273         if(defined($ldap_handle)) {
2274                 daemon_log("$session_id INFO: create_fai_server_db: start", 5);
2275                 my $mesg= $ldap_handle->search(
2276                         base   => $ldap_base,
2277                         scope  => 'sub',
2278                         attrs  => ['FAIrepository', 'gosaUnitTag'],
2279                         filter => "(&(FAIrepository=*)(objectClass=FAIrepositoryServer))",
2280                 );
2281                 if($mesg->{'resultCode'} == 0 &&
2282                    $mesg->count != 0) {
2283                    foreach my $entry (@{$mesg->{entries}}) {
2284                            if($entry->exists('FAIrepository')) {
2285                                    # Add an entry for each Repository configured for server
2286                                    foreach my $repo(@{$entry->get_value('FAIrepository', asref => 1)}) {
2287                                                    my($tmp_url,$tmp_server,$tmp_release,$tmp_sections) = split(/\|/, $repo);
2288                                                    my $tmp_tag= $entry->get_value('gosaUnitTag') || "";
2289                                                    $result= $fai_server_db->add_dbentry( { 
2290                                                                    table => $table_name,
2291                                                                    primkey => ['server', 'release', 'tag'],
2292                                                                    server => $tmp_url,
2293                                                                    release => $tmp_release,
2294                                                                    sections => $tmp_sections,
2295                                                                    tag => (length($tmp_tag)>0)?$tmp_tag:"",
2296                                                            } );
2297                                            }
2298                                    }
2299                            }
2300                    }
2301                 daemon_log("$session_id INFO: create_fai_server_db: finished", 5);
2303                 # TODO: Find a way to post the 'create_packages_list_db' event
2304                 if(not defined($dont_create_packages_list)) {
2305                         &create_packages_list_db(undef, undef, $session_id);
2306                 }
2307         }       
2308     
2309     $ldap_handle->disconnect;
2310         return $result;
2314 sub run_create_fai_release_db {
2315     my ($session, $heap, $table_name) = @_[SESSION, HEAP, ARG0];
2316         my $session_id = $session->ID;
2317     my $task = POE::Wheel::Run->new(
2318             Program => sub { &create_fai_release_db($table_name, $session_id) },
2319             StdoutEvent  => "session_run_result",
2320             StderrEvent  => "session_run_debug",
2321             CloseEvent   => "session_run_done",
2322             );
2324     $heap->{task}->{ $task->ID } = $task;
2325     return;
2329 sub create_fai_release_db {
2330         my ($table_name, $session_id) = @_;
2331         my $result;
2333     # used for logging
2334     if (not defined $session_id) { $session_id = 0; }
2336     my $ldap_handle = &get_ldap_handle();
2337         if(defined($ldap_handle)) {
2338                 daemon_log("$session_id INFO: create_fai_release_db: start",5);
2339                 my $mesg= $ldap_handle->search(
2340                         base   => $ldap_base,
2341                         scope  => 'sub',
2342                         attrs  => [],
2343                         filter => "(&(objectClass=organizationalUnit)(ou=fai))",
2344                 );
2345                 if($mesg->{'resultCode'} == 0 &&
2346                         $mesg->count != 0) {
2347                         # Walk through all possible FAI container ou's
2348                         my @sql_list;
2349                         my $timestamp= &get_time();
2350                         foreach my $ou (@{$mesg->{entries}}) {
2351                                 my $tmp_classes= resolve_fai_classes($ou->dn, $ldap_handle, $session_id);
2352                                 if(defined($tmp_classes) && ref($tmp_classes) eq 'HASH') {
2353                                         my @tmp_array=get_fai_release_entries($tmp_classes);
2354                                         if(@tmp_array) {
2355                                                 foreach my $entry (@tmp_array) {
2356                                                         if(defined($entry) && ref($entry) eq 'HASH') {
2357                                                                 my $sql= 
2358                                                                 "INSERT INTO $table_name "
2359                                                                 ."(timestamp, release, class, type, state) VALUES ("
2360                                                                 .$timestamp.","
2361                                                                 ."'".$entry->{'release'}."',"
2362                                                                 ."'".$entry->{'class'}."',"
2363                                                                 ."'".$entry->{'type'}."',"
2364                                                                 ."'".$entry->{'state'}."')";
2365                                                                 push @sql_list, $sql;
2366                                                         }
2367                                                 }
2368                                         }
2369                                 }
2370                         }
2372                         daemon_log("$session_id DEBUG: Inserting ".scalar @sql_list." entries to DB",8);
2373                         if(@sql_list) {
2374                                 unshift @sql_list, "VACUUM";
2375                                 unshift @sql_list, "DELETE FROM $table_name";
2376                                 $fai_release_db->exec_statementlist(\@sql_list);
2377                         }
2378                         daemon_log("$session_id DEBUG: Done with inserting",7);
2379                 }
2380                 daemon_log("$session_id INFO: create_fai_release_db: finished",5);
2381         }
2382     $ldap_handle->disconnect;
2383         return $result;
2386 sub get_fai_types {
2387         my $tmp_classes = shift || return undef;
2388         my @result;
2390         foreach my $type(keys %{$tmp_classes}) {
2391                 if(defined($tmp_classes->{$type}[0]) && (!($tmp_classes->{$type}[0] =~ /^.*?removed.*?$/))) {
2392                         my $entry = {
2393                                 type => $type,
2394                                 state => $tmp_classes->{$type}[0],
2395                         };
2396                         push @result, $entry;
2397                 }
2398         }
2400         return @result;
2403 sub get_fai_state {
2404         my $result = "";
2405         my $tmp_classes = shift || return $result;
2407         foreach my $type(keys %{$tmp_classes}) {
2408                 if(defined($tmp_classes->{$type}[0])) {
2409                         $result = $tmp_classes->{$type}[0];
2410                         
2411                 # State is equal for all types in class
2412                         last;
2413                 }
2414         }
2416         return $result;
2419 sub resolve_fai_classes {
2420         my ($fai_base, $ldap_handle, $session_id) = @_;
2421         if (not defined $session_id) { $session_id = 0; }
2422         my $result;
2423         my @possible_fai_classes= ("FAIscript", "FAIhook", "FAIpartitionTable", "FAItemplate", "FAIvariable", "FAIprofile", "FAIpackageList");
2424         my $fai_filter= "(|(&(objectClass=FAIclass)(|(objectClass=".join(")(objectClass=", @possible_fai_classes).")))(objectClass=FAIbranch))";
2425         my $fai_classes;
2427         daemon_log("$session_id DEBUG: Searching for FAI entries in base $fai_base",7);
2428         my $mesg= $ldap_handle->search(
2429                 base   => $fai_base,
2430                 scope  => 'sub',
2431                 attrs  => ['cn','objectClass','FAIstate'],
2432                 filter => $fai_filter,
2433         );
2434         daemon_log("$session_id DEBUG: Found ".$mesg->count()." FAI entries",7);
2436         if($mesg->{'resultCode'} == 0 &&
2437                 $mesg->count != 0) {
2438                 foreach my $entry (@{$mesg->{entries}}) {
2439                         if($entry->exists('cn')) {
2440                                 my $tmp_dn= $entry->dn();
2442                                 # Skip classname and ou dn parts for class
2443                                 my $tmp_release = ($1) if $tmp_dn =~ /^[^,]+,[^,]+,(.*?),$fai_base$/;
2445                                 # Skip classes without releases
2446                                 if((!defined($tmp_release)) || length($tmp_release)==0) {
2447                                         next;
2448                                 }
2450                                 my $tmp_cn= $entry->get_value('cn');
2451                                 my $tmp_state= $entry->get_value('FAIstate');
2453                                 my $tmp_type;
2454                                 # Get FAI type
2455                                 for my $oclass(@{$entry->get_value('objectClass', asref => 1)}) {
2456                                         if(grep $_ eq $oclass, @possible_fai_classes) {
2457                                                 $tmp_type= $oclass;
2458                                                 last;
2459                                         }
2460                                 }
2462                                 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
2463                                         # A Subrelease
2464                                         my @sub_releases = split(/,/, $tmp_release);
2466                                         # Walk through subreleases and build hash tree
2467                                         my $hash;
2468                                         while(my $tmp_sub_release = pop @sub_releases) {
2469                                                 $hash .= "\{'$tmp_sub_release'\}->";                                            
2470                                         }
2471                                         eval('push @{$fai_classes->'.$hash.'{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";');
2472                                 } else {
2473                                         # A branch, no subrelease
2474                                         push @{$fai_classes->{$tmp_release}->{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";
2475                                 }
2476                         } elsif (!$entry->exists('cn')) {
2477                                 my $tmp_dn= $entry->dn();
2478                                 my $tmp_release = ($1) if $tmp_dn =~ /^(.*?),$fai_base$/;
2480                                 # Skip classes without releases
2481                                 if((!defined($tmp_release)) || length($tmp_release)==0) {
2482                                         next;
2483                                 }
2485                                 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
2486                                         # A Subrelease
2487                                         my @sub_releases= split(/,/, $tmp_release);
2489                                         # Walk through subreleases and build hash tree
2490                                         my $hash;
2491                                         while(my $tmp_sub_release = pop @sub_releases) {
2492                                                 $hash .= "\{'$tmp_sub_release'\}->";                                            
2493                                         }
2494                                         # Remove the last two characters
2495                                         chop($hash);
2496                                         chop($hash);
2498                                         eval('$fai_classes->'.$hash.'= {}');
2499                                 } else {
2500                                         # A branch, no subrelease
2501                                         if(!exists($fai_classes->{$tmp_release})) {
2502                                                 $fai_classes->{$tmp_release} = {};
2503                                         }
2504                                 }
2505                         }
2506                 }
2508                 # The hash is complete, now we can honor the copy-on-write based missing entries
2509                 foreach my $release (keys %$fai_classes) {
2510                         $result->{$release}= deep_copy(apply_fai_inheritance($fai_classes->{$release}));
2511                 }
2512         }
2513         return $result;
2516 sub apply_fai_inheritance {
2517        my $fai_classes = shift || return {};
2518        my $tmp_classes;
2520        # Get the classes from the branch
2521        foreach my $class (keys %{$fai_classes}) {
2522                # Skip subreleases
2523                if($class =~ /^ou=.*$/) {
2524                        next;
2525                } else {
2526                        $tmp_classes->{$class}= deep_copy($fai_classes->{$class});
2527                }
2528        }
2530        # Apply to each subrelease
2531        foreach my $subrelease (keys %{$fai_classes}) {
2532                if($subrelease =~ /ou=/) {
2533                        foreach my $tmp_class (keys %{$tmp_classes}) {
2534                                if(!exists($fai_classes->{$subrelease}->{$tmp_class})) {
2535                                        $fai_classes->{$subrelease}->{$tmp_class} =
2536                                        deep_copy($tmp_classes->{$tmp_class});
2537                                } else {
2538                                        foreach my $type (keys %{$tmp_classes->{$tmp_class}}) {
2539                                                if(!exists($fai_classes->{$subrelease}->{$tmp_class}->{$type})) {
2540                                                        $fai_classes->{$subrelease}->{$tmp_class}->{$type}=
2541                                                        deep_copy($tmp_classes->{$tmp_class}->{$type});
2542                                                }
2543                                        }
2544                                }
2545                        }
2546                }
2547        }
2549        # Find subreleases in deeper levels
2550        foreach my $subrelease (keys %{$fai_classes}) {
2551                if($subrelease =~ /ou=/) {
2552                        foreach my $subsubrelease (keys %{$fai_classes->{$subrelease}}) {
2553                                if($subsubrelease =~ /ou=/) {
2554                                        apply_fai_inheritance($fai_classes->{$subrelease});
2555                                }
2556                        }
2557                }
2558        }
2560        return $fai_classes;
2563 sub get_fai_release_entries {
2564         my $tmp_classes = shift || return;
2565         my $parent = shift || "";
2566         my @result = shift || ();
2568         foreach my $entry (keys %{$tmp_classes}) {
2569                 if(defined($entry)) {
2570                         if($entry =~ /^ou=.*$/) {
2571                                 my $release_name = $entry;
2572                                 $release_name =~ s/ou=//g;
2573                                 if(length($parent)>0) {
2574                                         $release_name = $parent."/".$release_name;
2575                                 }
2576                                 my @bufentries = get_fai_release_entries($tmp_classes->{$entry}, $release_name, @result);
2577                                 foreach my $bufentry(@bufentries) {
2578                                         push @result, $bufentry;
2579                                 }
2580                         } else {
2581                                 my @types = get_fai_types($tmp_classes->{$entry});
2582                                 foreach my $type (@types) {
2583                                         push @result, 
2584                                         {
2585                                                 'class' => $entry,
2586                                                 'type' => $type->{'type'},
2587                                                 'release' => $parent,
2588                                                 'state' => $type->{'state'},
2589                                         };
2590                                 }
2591                         }
2592                 }
2593         }
2595         return @result;
2598 sub deep_copy {
2599         my $this = shift;
2600         if (not ref $this) {
2601                 $this;
2602         } elsif (ref $this eq "ARRAY") {
2603                 [map deep_copy($_), @$this];
2604         } elsif (ref $this eq "HASH") {
2605                 +{map { $_ => deep_copy($this->{$_}) } keys %$this};
2606         } else { die "what type is $_?" }
2610 sub session_run_result {
2611     my ($kernel, $heap, $client_answer) = @_[KERNEL, HEAP, ARG0];    
2612     $kernel->sig(CHLD => "child_reap");
2615 sub session_run_debug {
2616     my $result = $_[ARG0];
2617     print STDERR "$result\n";
2620 sub session_run_done {
2621     my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
2622     delete $heap->{task}->{$task_id};
2626 sub create_sources_list {
2627         my $session_id = shift;
2628         my $ldap_handle = &main::get_ldap_handle;
2629         my $result="/tmp/gosa_si_tmp_sources_list";
2631         # Remove old file
2632         if(stat($result)) {
2633                 unlink($result);
2634                 &main::daemon_log("$session_id DEBUG: remove an old version of '$result'", 7); 
2635         }
2637         my $fh;
2638         open($fh, ">$result");
2639         if (not defined $fh) {
2640                 &main::daemon_log("$session_id DEBUG: cannot open '$result' for writing", 7); 
2641                 return undef;
2642         }
2643         if(defined($main::ldap_server_dn) and length($main::ldap_server_dn) > 0) {
2644                 my $mesg=$ldap_handle->search(
2645                         base    => $main::ldap_server_dn,
2646                         scope   => 'base',
2647                         attrs   => 'FAIrepository',
2648                         filter  => 'objectClass=FAIrepositoryServer'
2649                 );
2650                 if($mesg->count) {
2651                         foreach my $entry(@{$mesg->{'entries'}}) {
2652                                 foreach my $value(@{$entry->get_value('FAIrepository', asref => 1)}) {
2653                                         my ($server, $tag, $release, $sections)= split /\|/, $value;
2654                                         my $line = "deb $server $release";
2655                                         $sections =~ s/,/ /g;
2656                                         $line.= " $sections";
2657                                         print $fh $line."\n";
2658                                 }
2659                         }
2660                 }
2661         } else {
2662                 if (defined $main::ldap_server_dn){
2663                         &main::daemon_log("$session_id ERROR: something wrong with ldap_server_dn '$main::ldap_server_dn', abort create_sources_list", 1); 
2664                 } else {
2665                         &main::daemon_log("$session_id ERROR: no ldap_server_dn found, abort create_sources_list", 1);
2666                 }
2667         }
2668         close($fh);
2670         return $result;
2674 sub run_create_packages_list_db {
2675     my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
2676         my $session_id = $session->ID;
2678         my $task = POE::Wheel::Run->new(
2679                                         Priority => +20,
2680                                         Program => sub {&create_packages_list_db(undef, undef, $session_id)},
2681                                         StdoutEvent  => "session_run_result",
2682                                         StderrEvent  => "session_run_debug",
2683                                         CloseEvent   => "session_run_done",
2684                                         );
2685         $heap->{task}->{ $task->ID } = $task;
2689 sub create_packages_list_db {
2690         my ($ldap_handle, $sources_file, $session_id) = @_;
2691         
2692         # it should not be possible to trigger a recreation of packages_list_db
2693         # while packages_list_db is under construction, so set flag packages_list_under_construction
2694         # which is tested befor recreation can be started
2695         if (-r $packages_list_under_construction) {
2696                 daemon_log("$session_id WARNING: packages_list_db is right now under construction, please wait until this process is finished", 3);
2697                 return;
2698         } else {
2699                 daemon_log("$session_id INFO: create_packages_list_db: start", 5); 
2700                 # set packages_list_under_construction to true
2701                 system("touch $packages_list_under_construction");
2702                 @packages_list_statements=();
2703         }
2705         if (not defined $session_id) { $session_id = 0; }
2706         if (not defined $ldap_handle) { 
2707                 $ldap_handle= &get_ldap_handle();
2709                 if (not defined $ldap_handle) {
2710                         daemon_log("$session_id ERROR: no ldap_handle available to create_packages_list_db", 1);
2711                         unlink($packages_list_under_construction);
2712                         return;
2713                 }
2714         }
2715         if (not defined $sources_file) { 
2716                 &main::daemon_log("$session_id INFO: no sources_file given for creating packages list so trigger creation of it", 5); 
2717                 $sources_file = &create_sources_list($session_id);
2718         }
2720         if (not defined $sources_file) {
2721                 &main::daemon_log("$session_id ERROR: no sources_file given under '$sources_file', skip create_packages_list_db", 1); 
2722                 unlink($packages_list_under_construction);
2723                 return;
2724         }
2726         my $line;
2728         open(CONFIG, "<$sources_file") or do {
2729                 daemon_log( "$session_id ERROR: create_packages_list_db: Failed to open '$sources_file'", 1);
2730                 unlink($packages_list_under_construction);
2731                 return;
2732         };
2734         # Read lines
2735         while ($line = <CONFIG>){
2736                 # Unify
2737                 chop($line);
2738                 $line =~ s/^\s+//;
2739                 $line =~ s/^\s+/ /;
2741                 # Strip comments
2742                 $line =~ s/#.*$//g;
2744                 # Skip empty lines
2745                 if ($line =~ /^\s*$/){
2746                         next;
2747                 }
2749                 # Interpret deb line
2750                 if ($line =~ /^deb [^\s]+\s[^\s]+\s[^\s]+/){
2751                         my( $baseurl, $dist, $sections ) = ($line =~ /^deb\s([^\s]+)\s+([^\s]+)\s+(.*)$/);
2752                         my $section;
2753                         foreach $section (split(' ', $sections)){
2754                                 &parse_package_info( $baseurl, $dist, $section, $session_id );
2755                         }
2756                 }
2757         }
2759         close (CONFIG);
2762         find(\&cleanup_and_extract, keys( %repo_dirs ));
2763         &main::strip_packages_list_statements();
2764         unshift @packages_list_statements, "VACUUM";
2765         $packages_list_db->exec_statementlist(\@packages_list_statements);
2766         unlink($packages_list_under_construction);
2767         daemon_log("$session_id INFO: create_packages_list_db: finished", 5); 
2768         return;
2771 # This function should do some intensive task to minimize the db-traffic
2772 sub strip_packages_list_statements {
2773     my @existing_entries= @{$packages_list_db->exec_statement("SELECT * FROM $main::packages_list_tn")};
2774         my @new_statement_list=();
2775         my $hash;
2776         my $insert_hash;
2777         my $update_hash;
2778         my $delete_hash;
2779         my $local_timestamp=get_time();
2781         foreach my $existing_entry (@existing_entries) {
2782                 $hash->{@{$existing_entry}[0]}->{@{$existing_entry}[1]}->{@{$existing_entry}[2]}= $existing_entry;
2783         }
2785         foreach my $statement (@packages_list_statements) {
2786                 if($statement =~ /^INSERT/i) {
2787                         # Assign the values from the insert statement
2788                         my ($distribution,$package,$version,$section,$description,$template,$timestamp) = ($1,$2,$3,$4,$5,$6,$7) if $statement =~ 
2789                         /^INSERT\s+?INTO\s+?$main::packages_list_tn\s+?VALUES\s*?\('(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)'\s*?\)$/si;
2790                         if(exists($hash->{$distribution}->{$package}->{$version})) {
2791                                 # If section or description has changed, update the DB
2792                                 if( 
2793                                         (! (@{$hash->{$distribution}->{$package}->{$version}}[3] eq $section)) or 
2794                                         (! (@{$hash->{$distribution}->{$package}->{$version}}[4] eq $description))
2795                                 ) {
2796                                         @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,undef);
2797                                 }
2798                         } else {
2799                                 # Insert a non-existing entry to db
2800                                 @{$insert_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
2801                         }
2802                 } elsif ($statement =~ /^UPDATE/i) {
2803                         my ($template,$package,$version) = ($1,$2,$3) if $statement =~
2804                         /^update\s+?$main::packages_list_tn\s+?set\s+?template\s*?=\s*?'(.*?)'\s+?where\s+?package\s*?=\s*?'(.*?)'\s+?and\s+?version\s*?=\s*?'(.*?)'\s*?;$/si;
2805                         foreach my $distribution (keys %{$hash}) {
2806                                 if(exists($insert_hash->{$distribution}->{$package}->{$version})) {
2807                                         # update the insertion hash to execute only one query per package (insert instead insert+update)
2808                                         @{$insert_hash->{$distribution}->{$package}->{$version}}[5]= $template;
2809                                 } elsif(exists($hash->{$distribution}->{$package}->{$version})) {
2810                                         if( ! (@{$hash->{$distribution}->{$package}->{$version}}[5] eq $template)) {
2811                                                 my $section;
2812                                                 my $description;
2813                                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) and
2814                                                         length(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) > 0 ) {
2815                                                         $section= @{$update_hash->{$distribution}->{$package}->{$version}}[3];
2816                                                 }
2817                                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
2818                                                         $description= @{$update_hash->{$distribution}->{$package}->{$version}}[4];
2819                                                 }
2820                                                 @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
2821                                         }
2822                                 }
2823                         }
2824                 }
2825         }
2827         # TODO: Check for orphaned entries
2829         # unroll the insert_hash
2830         foreach my $distribution (keys %{$insert_hash}) {
2831                 foreach my $package (keys %{$insert_hash->{$distribution}}) {
2832                         foreach my $version (keys %{$insert_hash->{$distribution}->{$package}}) {
2833                                 push @new_statement_list, "INSERT INTO $main::packages_list_tn VALUES ('$distribution','$package','$version',"
2834                                 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[3]',"
2835                                 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[4]',"
2836                                 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[5]',"
2837                                 ."'$local_timestamp')";
2838                         }
2839                 }
2840         }
2842         # unroll the update hash
2843         foreach my $distribution (keys %{$update_hash}) {
2844                 foreach my $package (keys %{$update_hash->{$distribution}}) {
2845                         foreach my $version (keys %{$update_hash->{$distribution}->{$package}}) {
2846                                 my $set = "";
2847                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3])) {
2848                                         $set .= "section = '@{$update_hash->{$distribution}->{$package}->{$version}}[3]', ";
2849                                 }
2850                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
2851                                         $set .= "description = '@{$update_hash->{$distribution}->{$package}->{$version}}[4]', ";
2852                                 }
2853                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[5])) {
2854                                         $set .= "template = '@{$update_hash->{$distribution}->{$package}->{$version}}[5]', ";
2855                                 }
2856                                 if(defined($set) and length($set) > 0) {
2857                                         $set .= "timestamp = '$local_timestamp'";
2858                                 } else {
2859                                         next;
2860                                 }
2861                                 push @new_statement_list, 
2862                                         "UPDATE $main::packages_list_tn SET $set WHERE"
2863                                         ." distribution = '$distribution'"
2864                                         ." AND package = '$package'"
2865                                         ." AND version = '$version'";
2866                         }
2867                 }
2868         }
2870         @packages_list_statements = @new_statement_list;
2874 sub parse_package_info {
2875     my ($baseurl, $dist, $section, $session_id)= @_;
2876     my ($package);
2877     if (not defined $session_id) { $session_id = 0; }
2878     my ($path) = ($baseurl =~ m%://[^/]*(.*)$%);
2879     $repo_dirs{ "${repo_path}/pool" } = 1;
2881     foreach $package ("Packages.gz"){
2882         daemon_log("$session_id DEBUG: create_packages_list: fetch $baseurl, $dist, $section", 7);
2883         get_package( "$baseurl/dists/$dist/$section/binary-$arch/$package", "$outdir/$dist/$section", $session_id );
2884         parse_package( "$outdir/$dist/$section", $dist, $path, $session_id );
2885     }
2886     
2890 sub get_package {
2891     my ($url, $dest, $session_id)= @_;
2892     if (not defined $session_id) { $session_id = 0; }
2894     my $tpath = dirname($dest);
2895     -d "$tpath" || mkpath "$tpath";
2897     # This is ugly, but I've no time to take a look at "how it works in perl"
2898     if(0 == system("wget '$url' -O '$dest' 2>/dev/null") ) {
2899         system("gunzip -cd '$dest' > '$dest.in'");
2900         daemon_log("$session_id DEBUG: run command: gunzip -cd '$dest' > '$dest.in'", 5);
2901         unlink($dest);
2902         daemon_log("$session_id DEBUG: delete file '$dest'", 5); 
2903     } else {
2904         daemon_log("$session_id ERROR: create_packages_list_db: get_packages: fetching '$url' failed!", 1);
2905     }
2906     return 0;
2910 sub parse_package {
2911     my ($path, $dist, $srv_path, $session_id)= @_;
2912     if (not defined $session_id) { $session_id = 0;}
2913     my ($package, $version, $section, $description);
2914     my $PACKAGES;
2915     my $timestamp = &get_time();
2917     if(not stat("$path.in")) {
2918         daemon_log("$session_id ERROR: create_packages_list: parse_package: file '$path.in' is not readable",1);
2919         return;
2920     }
2922     open($PACKAGES, "<$path.in");
2923     if(not defined($PACKAGES)) {
2924         daemon_log("$session_id ERROR: create_packages_list_db: parse_package: cannot open '$path.in'",1); 
2925         return;
2926     }
2928     # Read lines
2929     while (<$PACKAGES>){
2930         my $line = $_;
2931         # Unify
2932         chop($line);
2934         # Use empty lines as a trigger
2935         if ($line =~ /^\s*$/){
2936             my $sql = "INSERT INTO packages_list VALUES ('$dist', '$package', '$version', '$section', '$description', '', '$timestamp')";
2937             push(@packages_list_statements, $sql);
2938             $package = "none";
2939             $version = "none";
2940             $section = "none";
2941             $description = "none"; 
2942             next;
2943         }
2945         # Trigger for package name
2946         if ($line =~ /^Package:\s/){
2947             ($package)= ($line =~ /^Package: (.*)$/);
2948             next;
2949         }
2951         # Trigger for version
2952         if ($line =~ /^Version:\s/){
2953             ($version)= ($line =~ /^Version: (.*)$/);
2954             next;
2955         }
2957         # Trigger for description
2958         if ($line =~ /^Description:\s/){
2959             ($description)= &encode_base64(($line =~ /^Description: (.*)$/));
2960             next;
2961         }
2963         # Trigger for section
2964         if ($line =~ /^Section:\s/){
2965             ($section)= ($line =~ /^Section: (.*)$/);
2966             next;
2967         }
2969         # Trigger for filename
2970         if ($line =~ /^Filename:\s/){
2971             my ($filename) = ($line =~ /^Filename: (.*)$/);
2972             store_fileinfo( $package, $filename, $dist, $srv_path, $version, $repo_path );
2973             next;
2974         }
2975     }
2977     close( $PACKAGES );
2978     unlink( "$path.in" );
2982 sub store_fileinfo {
2983     my( $package, $file, $dist, $path, $vers, $srvdir) = @_;
2985     my %fileinfo = (
2986         'package' => $package,
2987         'dist' => $dist,
2988         'version' => $vers,
2989     );
2991     $repo_files{ "${srvdir}/$file" } = \%fileinfo;
2995 sub cleanup_and_extract {
2996     my $fileinfo = $repo_files{ $File::Find::name };
2998     if( defined $fileinfo ) {
3000         my $dir = "$outdir/$fileinfo->{ 'dist' }/debconf.d";
3001         my $sql;
3002         my $package = $fileinfo->{ 'package' };
3003         my $newver = $fileinfo->{ 'version' };
3005         mkpath($dir);
3006         system( "dpkg -e '$File::Find::name' '$dir/DEBIAN'" );
3008                 if( -f "$dir/DEBIAN/templates" ) {
3010                         daemon_log("DEBUG: Found debconf templates in '$package' - $newver", 7);
3012                         my $tmpl= "";
3013                         {
3014                                 local $/=undef;
3015                                 open FILE, "$dir/DEBIAN/templates";
3016                                 $tmpl = &encode_base64(<FILE>);
3017                                 close FILE;
3018                         }
3019                         rmtree("$dir/DEBIAN/templates");
3021                         $sql= "update $main::packages_list_tn set template = '$tmpl' where package = '$package' and version = '$newver';";
3022                 push @packages_list_statements, $sql;
3023                 }
3024     }
3026     return;
3030 sub register_at_foreign_servers {   
3031     my ($kernel) = $_[KERNEL];
3033     # hole alle bekannten server aus known_server_db
3034     my $server_sql = "SELECT * FROM $known_server_tn";
3035     my $server_res = $known_server_db->exec_statement($server_sql);
3037     # no entries in known_server_db
3038     if (not ref(@$server_res[0]) eq "ARRAY") { 
3039         # TODO
3040     }
3042     # detect already connected clients
3043     my $client_sql = "SELECT * FROM $known_clients_tn"; 
3044     my $client_res = $known_clients_db->exec_statement($client_sql);
3046     # send my server details to all other gosa-si-server within the network
3047     foreach my $hit (@$server_res) {
3048         my $hostname = @$hit[0];
3049         my $hostkey = &create_passwd;
3051         # add already connected clients to registration message 
3052         my $myhash = &create_xml_hash('new_server', $server_address, $hostname);
3053         &add_content2xml_hash($myhash, 'key', $hostkey);
3054         map(&add_content2xml_hash($myhash, 'client', @{$_}[0].",".@{$_}[4]), @$client_res);
3056         # add locally loaded gosa-si modules to registration message
3057         my $loaded_modules = {};
3058         while (my ($package, $pck_info) = each %$known_modules) {
3059                                                 next if ((!defined(@$pck_info[2])) || (!(ref (@$pck_info[2]) eq 'HASH')));
3060                                                 foreach my $act_module (keys(%{@$pck_info[2]})) {
3061                                                         $loaded_modules->{$act_module} = ""; 
3062                                                 }
3063         }
3065         map(&add_content2xml_hash($myhash, "loaded_modules", $_), keys(%$loaded_modules));
3067         # add macaddress to registration message
3068         my ($host_ip, $host_port) = split(/:/, $hostname);
3069         my $local_ip = &get_local_ip_for_remote_ip($host_ip);
3070         my $network_interface= &get_interface_for_ip($local_ip);
3071         my $host_mac = &get_mac_for_interface($network_interface);
3072         &add_content2xml_hash($myhash, 'macaddress', $host_mac);
3073         
3074         # build registration message and send it
3075         my $foreign_server_msg = &create_xml_string($myhash);
3076         my $error = &send_msg_to_target($foreign_server_msg, $hostname, $ServerPackages_key, "new_server", 0); 
3077     }
3078     
3079     $kernel->delay_set("register_at_foreign_servers", $foreign_servers_register_delay); 
3080     return;
3084 #==== MAIN = main ==============================================================
3085 #  parse commandline options
3086 Getopt::Long::Configure( "bundling" );
3087 GetOptions("h|help" => \&usage,
3088         "c|config=s" => \$cfg_file,
3089         "f|foreground" => \$foreground,
3090         "v|verbose+" => \$verbose,
3091         "no-arp+" => \$no_arp,
3092            );
3094 #  read and set config parameters
3095 &check_cmdline_param ;
3096 &read_configfile($cfg_file, %cfg_defaults);
3097 &check_pid;
3099 $SIG{CHLD} = 'IGNORE';
3101 # forward error messages to logfile
3102 if( ! $foreground ) {
3103   open( STDIN,  '+>/dev/null' );
3104   open( STDOUT, '+>&STDIN'    );
3105   open( STDERR, '+>&STDIN'    );
3108 # Just fork, if we are not in foreground mode
3109 if( ! $foreground ) { 
3110     chdir '/'                 or die "Can't chdir to /: $!";
3111     $pid = fork;
3112     setsid                    or die "Can't start a new session: $!";
3113     umask 0;
3114 } else { 
3115     $pid = $$; 
3118 # Do something useful - put our PID into the pid_file
3119 if( 0 != $pid ) {
3120     open( LOCK_FILE, ">$pid_file" );
3121     print LOCK_FILE "$pid\n";
3122     close( LOCK_FILE );
3123     if( !$foreground ) { 
3124         exit( 0 ) 
3125     };
3128 # parse head url and revision from svn
3129 my $server_status_hash = { 'developmental'=>'revision', 'stable'=>'release'};
3130 $server_version =~ /^\$HeadURL: (\S+) \$:\$Rev: (\d+) \$$/;
3131 $server_headURL = defined $1 ? $1 : 'unknown' ;
3132 $server_revision = defined $2 ? $2 : 'unknown' ;
3133 if ($server_headURL =~ /\/tag\// || 
3134         $server_headURL =~ /\/branches\// ) {
3135     $server_status = "stable"; 
3136 } else {
3137     $server_status = "developmental" ;
3141 daemon_log(" ", 1);
3142 daemon_log("$0 started!", 1);
3143 daemon_log("status: $server_status", 1);
3144 daemon_log($server_status_hash->{$server_status}.": $server_revision", 1); 
3146 # connect to incoming_db
3147 unlink($incoming_file_name);
3148 $incoming_db = GOSA::DBsqlite->new($incoming_file_name);
3149 $incoming_db->create_table($incoming_tn, \@incoming_col_names);
3151 # connect to gosa-si job queue
3152 $job_db = GOSA::DBsqlite->new($job_queue_file_name);
3153 $job_db->create_table($job_queue_tn, \@job_queue_col_names);
3155 # connect to known_clients_db
3156 $known_clients_db = GOSA::DBsqlite->new($known_clients_file_name);
3157 $known_clients_db->create_table($known_clients_tn, \@known_clients_col_names);
3159 # connect to foreign_clients_db
3160 $foreign_clients_db = GOSA::DBsqlite->new($foreign_clients_file_name);
3161 $foreign_clients_db->create_table($foreign_clients_tn, \@foreign_clients_col_names);
3163 # connect to known_server_db
3164 unlink($known_server_file_name);
3165 $known_server_db = GOSA::DBsqlite->new($known_server_file_name);
3166 $known_server_db->create_table($known_server_tn, \@known_server_col_names);
3168 # connect to login_usr_db
3169 $login_users_db = GOSA::DBsqlite->new($login_users_file_name);
3170 $login_users_db->create_table($login_users_tn, \@login_users_col_names);
3172 # connect to fai_server_db and fai_release_db
3173 unlink($fai_server_file_name);
3174 $fai_server_db = GOSA::DBsqlite->new($fai_server_file_name);
3175 $fai_server_db->create_table($fai_server_tn, \@fai_server_col_names);
3177 unlink($fai_release_file_name);
3178 $fai_release_db = GOSA::DBsqlite->new($fai_release_file_name);
3179 $fai_release_db->create_table($fai_release_tn, \@fai_release_col_names);
3181 # connect to packages_list_db
3182 #unlink($packages_list_file_name);
3183 unlink($packages_list_under_construction);
3184 $packages_list_db = GOSA::DBsqlite->new($packages_list_file_name);
3185 $packages_list_db->create_table($packages_list_tn, \@packages_list_col_names);
3187 # connect to messaging_db
3188 $messaging_db = GOSA::DBsqlite->new($messaging_file_name);
3189 $messaging_db->create_table($messaging_tn, \@messaging_col_names);
3192 # create xml object used for en/decrypting
3193 $xml = new XML::Simple();
3196 # foreign servers 
3197 my @foreign_server_list;
3199 # add foreign server from cfg file
3200 if ($foreign_server_string ne "") {
3201     my @cfg_foreign_server_list = split(",", $foreign_server_string);
3202     foreach my $foreign_server (@cfg_foreign_server_list) {
3203         push(@foreign_server_list, $foreign_server);
3204     }
3206     daemon_log("0 INFO: found foreign server in config file: ".join(", ", @foreign_server_list), 5);
3209 # Perform a DNS lookup for server registration if flag is true
3210 if ($dns_lookup eq "true") {
3211     # Add foreign server from dns
3212     my @tmp_servers;
3213     if (not $server_domain) {
3214         # Try our DNS Searchlist
3215         for my $domain(get_dns_domains()) {
3216             chomp($domain);
3217             my ($tmp_domains, $error_string) = &get_server_addresses($domain);
3218             if(@$tmp_domains) {
3219                 for my $tmp_server(@$tmp_domains) {
3220                     push @tmp_servers, $tmp_server;
3221                 }
3222             }
3223         }
3224         if(@tmp_servers && length(@tmp_servers)==0) {
3225             daemon_log("0 WARNING: no foreign gosa-si-server found in DNS for domain '$server_domain'", 3);
3226         }
3227     } else {
3228         @tmp_servers = &get_server_addresses($server_domain);
3229         if( 0 == @tmp_servers ) {
3230             daemon_log("0 WARNING: no foreign gosa-si-server found in DNS for domain '$server_domain'", 3);
3231         }
3232     }
3234     daemon_log("0 INFO: found foreign server via DNS ".join(", ", @tmp_servers), 5);    
3236     foreach my $server (@tmp_servers) { 
3237         unshift(@foreign_server_list, $server); 
3238     }
3239 } else {
3240     daemon_log("0 INFO: DNS lookup for server registration is disabled", 5);
3244 # eliminate duplicate entries
3245 @foreign_server_list = &del_doubles(@foreign_server_list);
3246 my $all_foreign_server = join(", ", @foreign_server_list);
3247 daemon_log("0 INFO: found foreign server in config file and DNS: '$all_foreign_server'", 5);
3249 # add all found foreign servers to known_server
3250 my $act_timestamp = &get_time();
3251 foreach my $foreign_server (@foreign_server_list) {
3253         # do not add myself to known_server_db
3254         if (&is_local($foreign_server)) { next; }
3255         ######################################
3257     my $res = $known_server_db->add_dbentry( {table=>$known_server_tn, 
3258             primkey=>['hostname'],
3259             hostname=>$foreign_server,
3260             macaddress=>"",
3261             status=>'not_jet_registered',
3262             hostkey=>"none",
3263             loaded_modules => "none", 
3264             timestamp=>$act_timestamp,
3265             } );
3269 # Import all modules
3270 &import_modules;
3272 # Check wether all modules are gosa-si valid passwd check
3273 &password_check;
3275 # Prepare for using Opsi 
3276 if ($opsi_enabled eq "true") {
3277     use JSON::RPC::Client;
3278     use XML::Quote qw(:all);
3279     $opsi_url= "https://".$opsi_admin.":".$opsi_password."@".$opsi_server.":4447/rpc";
3280     $opsi_client = new JSON::RPC::Client;
3284 POE::Component::Server::TCP->new(
3285     Alias => "TCP_SERVER",
3286         Port => $server_port,
3287         ClientInput => sub {
3288         my ($kernel, $input) = @_[KERNEL, ARG0];
3289         push(@tasks, $input);
3290         push(@msgs_to_decrypt, $input);
3291         $kernel->yield("msg_to_decrypt");
3292         },
3293     InlineStates => {
3294         msg_to_decrypt => \&msg_to_decrypt,
3295         next_task => \&next_task,
3296         task_result => \&handle_task_result,
3297         task_done   => \&handle_task_done,
3298         task_debug  => \&handle_task_debug,
3299         child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!"  },
3300     }
3301 );
3303 daemon_log("start socket for incoming xml messages at port '$server_port' ", 1);
3305 # create session for repeatedly checking the job queue for jobs
3306 POE::Session->create(
3307         inline_states => {
3308                 _start => \&session_start,
3309         register_at_foreign_servers => \&register_at_foreign_servers,
3310         sig_handler => \&sig_handler,
3311         next_task => \&next_task,
3312         task_result => \&handle_task_result,
3313         task_done   => \&handle_task_done,
3314         task_debug  => \&handle_task_debug,
3315         watch_for_next_tasks => \&watch_for_next_tasks,
3316         watch_for_new_messages => \&watch_for_new_messages,
3317         watch_for_delivery_messages => \&watch_for_delivery_messages,
3318         watch_for_done_messages => \&watch_for_done_messages,
3319                 watch_for_new_jobs => \&watch_for_new_jobs,
3320         watch_for_modified_jobs => \&watch_for_modified_jobs,
3321         watch_for_done_jobs => \&watch_for_done_jobs,
3322         watch_for_opsi_jobs => \&watch_for_opsi_jobs,
3323         watch_for_old_known_clients => \&watch_for_old_known_clients,
3324         create_packages_list_db => \&run_create_packages_list_db,
3325         create_fai_server_db => \&run_create_fai_server_db,
3326         create_fai_release_db => \&run_create_fai_release_db,
3327                 recreate_packages_db => \&run_recreate_packages_db,
3328         session_run_result => \&session_run_result,
3329         session_run_debug => \&session_run_debug,
3330         session_run_done => \&session_run_done,
3331         child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!"  },
3332         }
3333 );
3336 POE::Kernel->run();
3337 exit;