Code

e4cc7185d2acf13542037bef68bc74183abc1a7c
[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::GosaSupportDaemon;
52 use POE qw(Component::Server::TCP Wheel::Run Filter::Reference);
53 use Net::LDAP;
54 use Net::LDAP::Util qw(:escape);
55 use Time::HiRes qw( usleep);
57 my $db_module = "DBsqlite";
58 {
59 no strict "refs";
60 require ("GOSA/".$db_module.".pm");
61 ("GOSA/".$db_module)->import;
62 daemon_log("0 INFO: importing database module '$db_module'", 1);
63 }
65 my $modules_path = "/usr/lib/gosa-si/modules";
66 use lib "/usr/lib/gosa-si/modules";
68 # revision number of server and program name
69 my $server_version = '$HeadURL: https://oss.gonicus.de/repositories/gosa/trunk/gosa-si/gosa-si-server $:$Rev: 10826 $';
70 my $server_headURL;
71 my $server_revision;
72 my $server_status;
73 our $prg= basename($0);
75 our $global_kernel;
76 my ($foreground, $ping_timeout);
77 my ($server);
78 my ($gosa_server, $job_queue_timeout, $job_queue_loop_delay);
79 my ($messaging_db_loop_delay);
80 my ($procid, $pid);
81 my ($arp_fifo);
82 my ($xml);
83 my $sources_list;
84 my $max_clients;
85 my %repo_files=();
86 my $repo_path;
87 my %repo_dirs=();
89 # Variables declared in config file are always set to 'our'
90 our (%cfg_defaults, $log_file, $pid_file, 
91     $server_ip, $server_port, $ClientPackages_key, $dns_lookup,
92     $arp_activ, $gosa_unit_tag,
93     $GosaPackages_key, $gosa_timeout,
94     $foreign_server_string, $server_domain, $ServerPackages_key, $foreign_servers_register_delay,
95     $wake_on_lan_passwd, $job_synchronization, $modified_jobs_loop_delay,
96     $arp_enabled, $arp_interface,
97     $opsi_enabled, $opsi_server, $opsi_admin, $opsi_password,
98                 $new_systems_ou,
99 );
101 # additional variable which should be globaly accessable
102 our $server_address;
103 our $server_mac_address;
104 our $gosa_address;
105 our $no_arp;
106 our $verbose;
107 our $forground;
108 our $cfg_file;
109 our ($ldap_uri, $ldap_base, $ldap_admin_dn, $ldap_admin_password, $ldap_server_dn);
110 our ($mysql_username, $mysql_password, $mysql_database, $mysql_host);
111 our $known_modules;
112 our $root_uid;
113 our $adm_gid;
116 # specifies the verbosity of the daemon_log
117 $verbose = 0 ;
119 # if foreground is not null, script will be not forked to background
120 $foreground = 0 ;
122 # specifies the timeout seconds while checking the online status of a registrating client
123 $ping_timeout = 5;
125 $no_arp = 0;
126 my $packages_list_under_construction = "/tmp/packages_list_creation_in_progress";
127 my @packages_list_statements;
128 my $watch_for_new_jobs_in_progress = 0;
130 # holds all incoming decrypted messages
131 our $incoming_db;
132 our $incoming_tn = 'incoming';
133 my $incoming_file_name;
134 my @incoming_col_names = ("id INTEGER PRIMARY KEY auto_increment",
135         "timestamp VARCHAR(14) DEFAULT 'none'", 
136         "headertag VARCHAR(255) DEFAULT 'none'",
137         "targettag VARCHAR(255) DEFAULT 'none'",
138         "xmlmessage TEXT",
139         "module VARCHAR(255) DEFAULT 'none'",
140         "sessionid VARCHAR(255) DEFAULT '0'",
141 );
143 # holds all gosa jobs
144 our $job_db;
145 our $job_queue_tn = 'jobs';
146 my $job_queue_file_name;
147 my @job_queue_col_names = ("id INTEGER PRIMARY KEY auto_increment",
148         "timestamp VARCHAR(14) DEFAULT 'none'", 
149         "status VARCHAR(255) DEFAULT 'none'", 
150         "result TEXT",
151         "progress VARCHAR(255) DEFAULT 'none'",
152         "headertag VARCHAR(255) DEFAULT 'none'",
153         "targettag VARCHAR(255) DEFAULT 'none'", 
154         "xmlmessage TEXT", 
155         "macaddress VARCHAR(17) DEFAULT 'none'",
156         "plainname VARCHAR(255) DEFAULT 'none'",
157         "siserver VARCHAR(255) DEFAULT 'none'",
158         "modified INTEGER DEFAULT '0'",
159 );
161 # holds all other gosa-si-server
162 our $known_server_db;
163 our $known_server_tn = "known_server";
164 my $known_server_file_name;
165 my @known_server_col_names = ("hostname VARCHAR(255)", "macaddress VARCHAR(17)", "status VARCHAR(255)", "hostkey VARCHAR(255)", "loaded_modules TEXT", "timestamp VARCHAR(14)");
167 # holds all registrated clients
168 our $known_clients_db;
169 our $known_clients_tn = "known_clients";
170 my $known_clients_file_name;
171 my @known_clients_col_names = ("hostname VARCHAR(255)", "status VARCHAR(255)", "hostkey VARCHAR(255)", "timestamp VARCHAR(14)", "macaddress VARCHAR(17)", "events TEXT", "keylifetime VARCHAR(255)");
173 # holds all registered clients at a foreign server
174 our $foreign_clients_db;
175 our $foreign_clients_tn = "foreign_clients"; 
176 my $foreign_clients_file_name;
177 my @foreign_clients_col_names = ("hostname VARCHAR(255)", "macaddress VARCHAR(17)", "regserver VARCHAR(255)", "timestamp VARCHAR(14)");
179 # holds all logged in user at each client 
180 our $login_users_db;
181 our $login_users_tn = "login_users";
182 my $login_users_file_name;
183 my @login_users_col_names = ("client VARCHAR(255)", "user VARCHAR(255)", "timestamp VARCHAR(14)");
185 # holds all fai server, the debian release and tag
186 our $fai_server_db;
187 our $fai_server_tn = "fai_server"; 
188 my $fai_server_file_name;
189 our @fai_server_col_names = ("timestamp VARCHAR(14)", "server VARCHAR(255)", "fai_release VARCHAR(255)", "sections VARCHAR(255)", "tag VARCHAR(255)"); 
191 our $fai_release_db;
192 our $fai_release_tn = "fai_release"; 
193 my $fai_release_file_name;
194 our @fai_release_col_names = ("timestamp VARCHAR(14)", "fai_release VARCHAR(255)", "class VARCHAR(255)", "type VARCHAR(255)", "state VARCHAR(255)"); 
196 # holds all packages available from different repositories
197 our $packages_list_db;
198 our $packages_list_tn = "packages_list";
199 my $packages_list_file_name;
200 our @packages_list_col_names = ("distribution VARCHAR(255)", "package VARCHAR(255)", "version VARCHAR(255)", "section VARCHAR(255)", "description TEXT", "template LONGBLOB", "timestamp VARCHAR(14)");
201 my $outdir = "/tmp/packages_list_db";
202 my $arch = "i386"; 
204 # holds all messages which should be delivered to a user
205 our $messaging_db;
206 our $messaging_tn = "messaging"; 
207 our @messaging_col_names = ("id INTEGER", "subject TEXT", "message_from VARCHAR(255)", "message_to VARCHAR(255)", 
208         "flag VARCHAR(255)", "direction VARCHAR(255)", "delivery_time VARCHAR(255)", "message TEXT", "timestamp VARCHAR(14)" );
209 my $messaging_file_name;
211 # path to directory to store client install log files
212 our $client_fai_log_dir = "/var/log/fai"; 
214 # queue which stores taskes until one of the $max_children children are ready to process the task
215 #my @tasks = qw();
216 my @msgs_to_decrypt = qw();
217 my $max_children = 2;
220 # loop delay for job queue to look for opsi jobs
221 my $job_queue_opsi_delay = 10;
222 our $opsi_client;
223 our $opsi_url;
224  
225 # Lifetime of logged in user information. If no update information comes after n seconds, 
226 # the user is expeceted to be no longer logged in or the host is no longer running. Because
227 # of this, the user is deleted from login_users_db
228 our $logged_in_user_date_of_expiry = 600;
231 %cfg_defaults = (
232 "general" => {
233     "log-file" => [\$log_file, "/var/run/".$prg.".log"],
234     "pid-file" => [\$pid_file, "/var/run/".$prg.".pid"],
235     },
236 "server" => {
237     "ip"                    => [\$server_ip, "0.0.0.0"],
238     "port"                  => [\$server_port, "20081"],
239     "known-clients"         => [\$known_clients_file_name, '/var/lib/gosa-si/clients.db' ],
240     "known-servers"         => [\$known_server_file_name, '/var/lib/gosa-si/servers.db'],
241     "incoming"              => [\$incoming_file_name, '/var/lib/gosa-si/incoming.db'],
242     "login-users"           => [\$login_users_file_name, '/var/lib/gosa-si/users.db'],
243     "fai-server"            => [\$fai_server_file_name, '/var/lib/gosa-si/fai_server.db'],
244     "fai-release"           => [\$fai_release_file_name, '/var/lib/gosa-si/fai_release.db'],
245     "packages-list"         => [\$packages_list_file_name, '/var/lib/gosa-si/packages.db'],
246     "messaging"             => [\$messaging_file_name, '/var/lib/gosa-si/messaging.db'],
247     "foreign-clients"       => [\$foreign_clients_file_name, '/var/lib/gosa-si/foreign_clients.db'],
248     "source-list"           => [\$sources_list, '/etc/apt/sources.list'],
249     "repo-path"             => [\$repo_path, '/srv/www/repository'],
250     "ldap-uri"              => [\$ldap_uri, ""],
251     "ldap-base"             => [\$ldap_base, ""],
252     "ldap-admin-dn"         => [\$ldap_admin_dn, ""],
253     "ldap-admin-password"   => [\$ldap_admin_password, ""],
254     "gosa-unit-tag"         => [\$gosa_unit_tag, ""],
255     "max-clients"           => [\$max_clients, 10],
256     "wol-password"          => [\$wake_on_lan_passwd, ""],
257                 "mysql-username"        => [\$mysql_username, "gosa_si"],
258                 "mysql-password"        => [\$mysql_password, ""],
259                 "mysql-database"        => [\$mysql_database, "gosa_si"],
260                 "mysql-host"            => [\$mysql_host, "127.0.0.1"],
261     },
262 "GOsaPackages" => {
263     "job-queue" => [\$job_queue_file_name, '/var/lib/gosa-si/jobs.db'],
264     "job-queue-loop-delay" => [\$job_queue_loop_delay, 3],
265     "messaging-db-loop-delay" => [\$messaging_db_loop_delay, 3],
266     "key" => [\$GosaPackages_key, "none"],
267                 "new-systems-ou" => [\$new_systems_ou, 'ou=workstations,ou=systems'],
268     },
269 "ClientPackages" => {
270     "key" => [\$ClientPackages_key, "none"],
271     "user-date-of-expiry" => [\$logged_in_user_date_of_expiry, 600],
272     },
273 "ServerPackages"=> {
274     "address"      => [\$foreign_server_string, ""],
275     "dns-lookup"            => [\$dns_lookup, "true"],
276     "domain"  => [\$server_domain, ""],
277     "key"     => [\$ServerPackages_key, "none"],
278     "key-lifetime" => [\$foreign_servers_register_delay, 120],
279     "job-synchronization-enabled" => [\$job_synchronization, "true"],
280     "synchronization-loop" => [\$modified_jobs_loop_delay, 5],
281     },
282 "ArpHandler" => {
283     "enabled"   => [\$arp_enabled, "true"],
284     "interface" => [\$arp_interface, "all"],
285         },
286 "Opsi" => {
287     "enabled"  => [\$opsi_enabled, "false"], 
288     "server"   => [\$opsi_server, "localhost"],
289     "admin"    => [\$opsi_admin, "opsi-admin"],
290     "password" => [\$opsi_password, "secret"],
291    },
293 );
296 #===  FUNCTION  ================================================================
297 #         NAME:  usage
298 #   PARAMETERS:  nothing
299 #      RETURNS:  nothing
300 #  DESCRIPTION:  print out usage text to STDERR
301 #===============================================================================
302 sub usage {
303     print STDERR << "EOF" ;
304 usage: $prg [-hvf] [-c config]
306            -h        : this (help) message
307            -c <file> : config file
308            -f        : foreground, process will not be forked to background
309            -v        : be verbose (multiple to increase verbosity)
310            -no-arp   : starts $prg without connection to arp module
311  
312 EOF
313     print "\n" ;
317 #===  FUNCTION  ================================================================
318 #         NAME:  logging
319 #   PARAMETERS:  level - string - default 'info'
320 #                msg - string -
321 #                facility - string - default 'LOG_DAEMON'
322 #      RETURNS:  nothing
323 #  DESCRIPTION:  function for logging
324 #===============================================================================
325 sub daemon_log {
326     # log into log_file
327     my( $msg, $level ) = @_;
328     if(not defined $msg) { return }
329     if(not defined $level) { $level = 1 }
330     if(defined $log_file){
331         open(LOG_HANDLE, ">>$log_file");
332         if(not defined open( LOG_HANDLE, ">>$log_file" )) {
333             print STDERR "cannot open $log_file: $!";
334             return 
335         }
336         chomp($msg);
337         #$msg =~s/\n//g;   # no newlines are allowed in log messages, this is important for later log parsing
338         if($level <= $verbose){
339             my ($seconds, $minutes, $hours, $monthday, $month,
340                     $year, $weekday, $yearday, $sommertime) = localtime(time);
341             $hours = $hours < 10 ? $hours = "0".$hours : $hours;
342             $minutes = $minutes < 10 ? $minutes = "0".$minutes : $minutes;
343             $seconds = $seconds < 10 ? $seconds = "0".$seconds : $seconds;
344             my @monthnames = ("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec");
345             $month = $monthnames[$month];
346             $monthday = $monthday < 10 ? $monthday = "0".$monthday : $monthday;
347             $year+=1900;
348             my $name = $prg;
350             my $log_msg = "$month $monthday $hours:$minutes:$seconds $name $msg\n";
351             print LOG_HANDLE $log_msg;
352             if( $foreground ) { 
353                 print STDERR $log_msg;
354             }
355         }
356         close( LOG_HANDLE );
357     }
361 #===  FUNCTION  ================================================================
362 #         NAME:  check_cmdline_param
363 #   PARAMETERS:  nothing
364 #      RETURNS:  nothing
365 #  DESCRIPTION:  validates commandline parameter
366 #===============================================================================
367 sub check_cmdline_param () {
368     my $err_config;
369     my $err_counter = 0;
370         if(not defined($cfg_file)) {
371                 $cfg_file = "/etc/gosa-si/server.conf";
372                 if(! -r $cfg_file) {
373                         $err_config = "please specify a config file";
374                         $err_counter += 1;
375                 }
376     }
377     if( $err_counter > 0 ) {
378         &usage( "", 1 );
379         if( defined( $err_config)) { print STDERR "$err_config\n"}
380         print STDERR "\n";
381         exit( -1 );
382     }
386 #===  FUNCTION  ================================================================
387 #         NAME:  check_pid
388 #   PARAMETERS:  nothing
389 #      RETURNS:  nothing
390 #  DESCRIPTION:  handels pid processing
391 #===============================================================================
392 sub check_pid {
393     $pid = -1;
394     # Check, if we are already running
395     if( open(LOCK_FILE, "<$pid_file") ) {
396         $pid = <LOCK_FILE>;
397         if( defined $pid ) {
398             chomp( $pid );
399             if( -f "/proc/$pid/stat" ) {
400                 my($stat) = `cat /proc/$pid/stat` =~ m/$pid \((.+)\).*/;
401                 if( $stat ) {
402                                         daemon_log("ERROR: Already running",1);
403                     close( LOCK_FILE );
404                     exit -1;
405                 }
406             }
407         }
408         close( LOCK_FILE );
409         unlink( $pid_file );
410     }
412     # create a syslog msg if it is not to possible to open PID file
413     if (not sysopen(LOCK_FILE, $pid_file, O_WRONLY|O_CREAT|O_EXCL, 0644)) {
414         my($msg) = "Couldn't obtain lockfile '$pid_file' ";
415         if (open(LOCK_FILE, '<', $pid_file)
416                 && ($pid = <LOCK_FILE>))
417         {
418             chomp($pid);
419             $msg .= "(PID $pid)\n";
420         } else {
421             $msg .= "(unable to read PID)\n";
422         }
423         if( ! ($foreground) ) {
424             openlog( $0, "cons,pid", "daemon" );
425             syslog( "warning", $msg );
426             closelog();
427         }
428         else {
429             print( STDERR " $msg " );
430         }
431         exit( -1 );
432     }
435 #===  FUNCTION  ================================================================
436 #         NAME:  import_modules
437 #   PARAMETERS:  module_path - string - abs. path to the directory the modules 
438 #                are stored
439 #      RETURNS:  nothing
440 #  DESCRIPTION:  each file in module_path which ends with '.pm' and activation 
441 #                state is on is imported by "require 'file';"
442 #===============================================================================
443 sub import_modules {
444     daemon_log(" ", 1);
446     if (not -e $modules_path) {
447         daemon_log("0 ERROR: cannot find directory or directory is not readable: $modules_path", 1);   
448     }
450     opendir (DIR, $modules_path) or die "ERROR while loading modules from directory $modules_path : $!\n";
451     while (defined (my $file = readdir (DIR))) {
452         if (not $file =~ /(\S*?).pm$/) {
453             next;
454         }
455                 my $mod_name = $1;
457         # ArpHandler switch
458         if( $file =~ /ArpHandler.pm/ ) {
459             if( $arp_enabled eq "false" ) { next; }
460         }
461         
462         eval { require $file; };
463         if ($@) {
464             daemon_log("0 ERROR: gosa-si-server could not load module $file", 1);
465             daemon_log("$@", 1);
466             exit;
467                 } else {
468                         my $info = eval($mod_name.'::get_module_info()');
469                         # Only load module if get_module_info() returns a non-null object
470                         if( $info ) {
471                                 my ($input_address, $input_key, $event_hash) = @{$info};
472                                 $known_modules->{$mod_name} = $info;
473                                 daemon_log("0 INFO: module $mod_name loaded", 5);
474                         }
475                 }
476     }   
478     close (DIR);
481 #===  FUNCTION  ================================================================
482 #         NAME:  password_check
483 #   PARAMETERS:  nothing
484 #      RETURNS:  nothing
485 #  DESCRIPTION:  escalates an critical error if two modules exist which are avaialable by 
486 #                the same password
487 #===============================================================================
488 sub password_check {
489     my $passwd_hash = {};
490     while (my ($mod_name, $mod_info) = each %$known_modules) {
491         my $mod_passwd = @$mod_info[1];
492         if (not defined $mod_passwd) { next; }
493         if (not exists $passwd_hash->{$mod_passwd}) {
494             $passwd_hash->{$mod_passwd} = $mod_name;
496         # escalates critical error
497         } else {
498             &daemon_log("0 ERROR: two loaded modules do have the same password. Please modify the 'key'-parameter in config file");
499             &daemon_log("0 ERROR: module='$mod_name' and module='".$passwd_hash->{$mod_passwd}."'");
500             exit( -1 );
501         }
502     }
507 #===  FUNCTION  ================================================================
508 #         NAME:  sig_int_handler
509 #   PARAMETERS:  signal - string - signal arose from system
510 #      RETURNS:  nothing
511 #  DESCRIPTION:  handels tasks to be done befor signal becomes active
512 #===============================================================================
513 sub sig_int_handler {
514     my ($signal) = @_;
516 #       if (defined($ldap_handle)) {
517 #               $ldap_handle->disconnect;
518 #       }
519     # TODO alle verbliebenden ldap verbindungen aus allen heaps beenden
520     
522     daemon_log("shutting down gosa-si-server", 1);
523     system("kill `ps -C gosa-si-server -o pid=`");
525 $SIG{INT} = \&sig_int_handler;
528 sub check_key_and_xml_validity {
529     my ($crypted_msg, $module_key, $session_id) = @_;
530     my $msg;
531     my $msg_hash;
532     my $error_string;
533     eval{
534         $msg = &decrypt_msg($crypted_msg, $module_key);
536         if ($msg =~ /<xml>/i){
537             $msg =~ s/\s+/ /g;  # just for better daemon_log
538             daemon_log("$session_id DEBUG: decrypted_msg: \n$msg", 9);
539             $msg_hash = $xml->XMLin($msg, ForceArray=>1);
541             ##############
542             # check header
543             if( not exists $msg_hash->{'header'} ) { die "no header specified"; }
544             my $header_l = $msg_hash->{'header'};
545             if( (1 > @{$header_l}) || ( ( 'HASH' eq ref @{$header_l}[0]) && (1 > keys %{@{$header_l}[0]}) ) ) { die 'empty header tag'; }
546             if( 1 < @{$header_l} ) { die 'more than one header specified'; }
547             my $header = @{$header_l}[0];
548             if( 0 == length $header) { die 'empty string in header tag'; }
550             ##############
551             # check source
552             if( not exists $msg_hash->{'source'} ) { die "no source specified"; }
553             my $source_l = $msg_hash->{'source'};
554             if( (1 > @{$source_l}) || ( ( 'HASH' eq ref @{$source_l}[0]) && (1 > keys %{@{$source_l}[0]}) ) ) { die 'empty source tag'; }
555             if( 1 < @{$source_l} ) { die 'more than one source specified'; }
556             my $source = @{$source_l}[0];
557             if( 0 == length $source) { die 'source error'; }
559             ##############
560             # check target
561             if( not exists $msg_hash->{'target'} ) { die "no target specified"; }
562             my $target_l = $msg_hash->{'target'};
563             if( (1 > @{$target_l}) || ( ('HASH' eq ref @{$target_l}[0]) && (1 > keys %{@{$target_l}[0]}) ) ) { die 'empty target tag'; }
564         }
565     };
566     if($@) {
567         daemon_log("$session_id ERROR: do not understand the message: $@", 1);
568         $msg = undef;
569         $msg_hash = undef;
570     }
572     return ($msg, $msg_hash);
576 sub check_outgoing_xml_validity {
577     my ($msg, $session_id) = @_;
579     my $msg_hash;
580     eval{
581         $msg_hash = $xml->XMLin($msg, ForceArray=>1);
583         ##############
584         # check header
585         my $header_l = $msg_hash->{'header'};
586         if( 1 != @{$header_l} ) {
587             die 'no or more than one headers specified';
588         }
589         my $header = @{$header_l}[0];
590         if( 0 == length $header) {
591             die 'header has length 0';
592         }
594         ##############
595         # check source
596         my $source_l = $msg_hash->{'source'};
597         if( 1 != @{$source_l} ) {
598             die 'no or more than 1 sources specified';
599         }
600         my $source = @{$source_l}[0];
601         if( 0 == length $source) {
602             die 'source has length 0';
603         }
605                                 # Check if source contains hostname instead of ip address
606                                 if(not $source =~ /^[a-z0-9\.]+:\d+$/i) {
607                                                 my ($hostname,$port) = split(/:/, $source);
608                                                 my $ip_address = inet_ntoa(scalar gethostbyname($hostname));
609                                                 if(defined($ip_address) && $ip_address =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/ && $port =~ /^\d+$/) {
610                                                         # Write ip address to $source variable
611                                                         $source = "$ip_address:$port";
612                                                 }
613                                 }
614         unless( $source =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
615                 $source =~ /^GOSA$/i) {
616             die "source '$source' is neither a complete ip-address with port nor 'GOSA'";
617         }
618         
619         ##############
620         # check target  
621         my $target_l = $msg_hash->{'target'};
622         if( 0 == @{$target_l} ) {
623             die "no targets specified";
624         }
625         foreach my $target (@$target_l) {
626             if( 0 == length $target) {
627                 die "target has length 0";
628             }
629             unless( $target =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
630                     $target =~ /^GOSA$/i ||
631                     $target =~ /^\*$/ ||
632                     $target =~ /KNOWN_SERVER/i ||
633                     $target =~ /JOBDB/i ||
634                     $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 ){
635                 die "target '$target' is not a complete ip-address with port or a valid target name or a mac-address";
636             }
637         }
638     };
639     if($@) {
640         daemon_log("$session_id ERROR: outgoing msg is not gosa-si envelope conform: $@", 1);
641         daemon_log("$@ ".(defined($msg) && length($msg)>0)?$msg:"Empty Message", 1);
642         $msg_hash = undef;
643     }
645     return ($msg_hash);
649 sub input_from_known_server {
650     my ($input, $remote_ip, $session_id) = @_ ;  
651     my ($msg, $msg_hash, $module);
653     my $sql_statement= "SELECT * FROM known_server";
654     my $query_res = $known_server_db->select_dbentry( $sql_statement ); 
656     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
657         my $host_name = $hit->{hostname};
658         if( not $host_name =~ "^$remote_ip") {
659             next;
660         }
661         my $host_key = $hit->{hostkey};
662         daemon_log("$session_id DEBUG: input_from_known_server: host_name: $host_name", 7);
663         daemon_log("$session_id DEBUG: input_from_known_server: host_key: $host_key", 7);
665         # check if module can open msg envelope with module key
666         my ($tmp_msg, $tmp_msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
667         if( (!$tmp_msg) || (!$tmp_msg_hash) ) {
668             daemon_log("$session_id DEBUG: input_from_known_server: deciphering raise error", 7);
669             daemon_log("$@", 8);
670             next;
671         }
672         else {
673             $msg = $tmp_msg;
674             $msg_hash = $tmp_msg_hash;
675             $module = "ServerPackages";
676             daemon_log("$session_id DEBUG: check_key_and_xml_validity... ok", 7);
677             last;
678         }
679     }
681     if( (!$msg) || (!$msg_hash) || (!$module) ) {
682         daemon_log("$session_id DEBUG: Incoming message is not from a known server", 7);
683     }
684   
685     return ($msg, $msg_hash, $module);
689 sub input_from_known_client {
690     my ($input, $remote_ip, $session_id) = @_ ;  
691     my ($msg, $msg_hash, $module);
693     my $sql_statement= "SELECT * FROM known_clients";
694     my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
695     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
696         my $host_name = $hit->{hostname};
697         if( not $host_name =~ /^$remote_ip:\d*$/) {
698                 next;
699                 }
700         my $host_key = $hit->{hostkey};
701         &daemon_log("$session_id DEBUG: input_from_known_client: host_name: $host_name", 7);
702         &daemon_log("$session_id DEBUG: input_from_known_client: host_key: $host_key", 7);
704         # check if module can open msg envelope with module key
705         ($msg, $msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
707         if( (!$msg) || (!$msg_hash) ) {
708             &daemon_log("$session_id DEGUG: input_from_known_client: deciphering raise error", 7);
709             &daemon_log("$@", 8);
710             next;
711         }
712         else {
713             $module = "ClientPackages";
714             daemon_log("$session_id DEBUG: check_key_and_xml_validity... ok", 7);
715             last;
716         }
717     }
719     if( (!$msg) || (!$msg_hash) || (!$module) ) {
720         &daemon_log("$session_id DEBUG: Incoming message is not from a known client", 7);
721     }
723     return ($msg, $msg_hash, $module);
727 sub input_from_unknown_host {
728         no strict "refs";
729         my ($input, $session_id) = @_ ;
730         my ($msg, $msg_hash, $module);
731         my $error_string;
733         my %act_modules = %$known_modules;
735         while( my ($mod, $info) = each(%act_modules)) {
737                 # check a key exists for this module
738                 my $module_key = ${$mod."_key"};
739                 if( not defined $module_key ) {
740                         if( $mod eq 'ArpHandler' ) {
741                                 next;
742                         }
743                         daemon_log("$session_id ERROR: no key specified in config file for $mod", 1);
744                         next;
745                 }
746                 daemon_log("$session_id DEBUG: $mod: $module_key", 7);
748                 # check if module can open msg envelope with module key
749                 ($msg, $msg_hash) = &check_key_and_xml_validity($input, $module_key, $session_id);
750                 if( (not defined $msg) || (not defined $msg_hash) ) {
751                         next;
752                 } else {
753                         $module = $mod;
754             daemon_log("$session_id DEBUG: check_key_and_xml_validity... ok", 7);
755                         last;
756                 }
757         }
759         if( (!$msg) || (!$msg_hash) || (!$module)) {
760                 daemon_log("$session_id DEBUG: Incoming message is not from an unknown host", 7);
761         }
763         return ($msg, $msg_hash, $module);
767 sub create_ciphering {
768     my ($passwd) = @_;
769         if((!defined($passwd)) || length($passwd)==0) {
770                 $passwd = "";
771         }
772     $passwd = substr(md5_hex("$passwd") x 32, 0, 32);
773     my $iv = substr(md5_hex('GONICUS GmbH'),0, 16);
774     my $my_cipher = Crypt::Rijndael->new($passwd , Crypt::Rijndael::MODE_CBC());
775     $my_cipher->set_iv($iv);
776     return $my_cipher;
780 sub encrypt_msg {
781     my ($msg, $key) = @_;
782     my $my_cipher = &create_ciphering($key);
783     my $len;
784     {
785             use bytes;
786             $len= 16-length($msg)%16;
787     }
788     $msg = "\0"x($len).$msg;
789     $msg = $my_cipher->encrypt($msg);
790     chomp($msg = &encode_base64($msg));
791     # there are no newlines allowed inside msg
792     $msg=~ s/\n//g;
793     return $msg;
797 sub decrypt_msg {
799     my ($msg, $key) = @_ ;
800     $msg = &decode_base64($msg);
801     my $my_cipher = &create_ciphering($key);
802     $msg = $my_cipher->decrypt($msg); 
803     $msg =~ s/\0*//g;
804     return $msg;
808 sub get_encrypt_key {
809     my ($target) = @_ ;
810     my $encrypt_key;
811     my $error = 0;
813     # target can be in known_server
814     if( not defined $encrypt_key ) {
815         my $sql_statement= "SELECT * FROM known_server WHERE hostname='$target'";
816         my $query_res = $known_server_db->select_dbentry( $sql_statement ); 
817         while( my ($hit_num, $hit) = each %{ $query_res } ) {    
818             my $host_name = $hit->{hostname};
819             if( $host_name ne $target ) {
820                 next;
821             }
822             $encrypt_key = $hit->{hostkey};
823             last;
824         }
825     }
827     # target can be in known_client
828     if( not defined $encrypt_key ) {
829         my $sql_statement= "SELECT * FROM known_clients WHERE hostname='$target'";
830         my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
831         while( my ($hit_num, $hit) = each %{ $query_res } ) {    
832             my $host_name = $hit->{hostname};
833             if( $host_name ne $target ) {
834                 next;
835             }
836             $encrypt_key = $hit->{hostkey};
837             last;
838         }
839     }
841     return $encrypt_key;
845 #===  FUNCTION  ================================================================
846 #         NAME:  open_socket
847 #   PARAMETERS:  PeerAddr string something like 192.168.1.1 or 192.168.1.1:10000
848 #                [PeerPort] string necessary if port not appended by PeerAddr
849 #      RETURNS:  socket IO::Socket::INET
850 #  DESCRIPTION:  open a socket to PeerAddr
851 #===============================================================================
852 sub open_socket {
853     my ($PeerAddr, $PeerPort) = @_ ;
854     if(defined($PeerPort)){
855         $PeerAddr = $PeerAddr.":".$PeerPort;
856     }
857     my $socket;
858     $socket = new IO::Socket::INET(PeerAddr => $PeerAddr,
859             Porto => "tcp",
860             Type => SOCK_STREAM,
861             Timeout => 5,
862             );
863     if(not defined $socket) {
864         return;
865     }
866 #    &daemon_log("DEBUG: open_socket: $PeerAddr", 7);
867     return $socket;
871 #sub get_local_ip_for_remote_ip {
872 #       my $remote_ip= shift;
873 #       my $result="0.0.0.0";
875 #       if($remote_ip =~ /^(\d\d?\d?\.){3}\d\d?\d?$/) {
876 #               if($remote_ip eq "127.0.0.1") {
877 #                       $result = "127.0.0.1";
878 #               } else {
879 #                       my $PROC_NET_ROUTE= ('/proc/net/route');
881 #                       open(PROC_NET_ROUTE, "<$PROC_NET_ROUTE")
882 #                               or die "Could not open $PROC_NET_ROUTE";
884 #                       my @ifs = <PROC_NET_ROUTE>;
886 #                       close(PROC_NET_ROUTE);
888 #                       # Eat header line
889 #                       shift @ifs;
890 #                       chomp @ifs;
891 #                       foreach my $line(@ifs) {
892 #                               my ($Iface,$Destination,$Gateway,$Flags,$RefCnt,$Use,$Metric,$Mask,$MTU,$Window,$IRTT)=split(/\s/, $line);
893 #                               my $destination;
894 #                               my $mask;
895 #                               my ($d,$c,$b,$a)=unpack('a2 a2 a2 a2', $Destination);
896 #                               $destination= sprintf("%d.%d.%d.%d", hex($a), hex($b), hex($c), hex($d));
897 #                               ($d,$c,$b,$a)=unpack('a2 a2 a2 a2', $Mask);
898 #                               $mask= sprintf("%d.%d.%d.%d", hex($a), hex($b), hex($c), hex($d));
899 #                               if(new NetAddr::IP($remote_ip)->within(new NetAddr::IP($destination, $mask))) {
900 #                                       # destination matches route, save mac and exit
901 #                                       $result= &get_ip($Iface);
902 #                                       last;
903 #                               }
904 #                       }
905 #               }
906 #       } else {
907 #               daemon_log("0 WARNING: get_local_ip_for_remote_ip() was called with a non-ip parameter: '$remote_ip'", 1);
908 #       }
909 #       return $result;
910 #}
913 sub send_msg_to_target {
914     my ($msg, $address, $encrypt_key, $msg_header, $session_id) = @_ ;
915     my $error = 0;
916     my $header;
917     my $timestamp = &get_time();
918     my $new_status;
919     my $act_status;
920     my ($sql_statement, $res);
921   
922     if( $msg_header ) {
923         $header = "'$msg_header'-";
924     } else {
925         $header = "";
926     }
928         # Patch the source ip
929         if($msg =~ /<source>0\.0\.0\.0:\d*?<\/source>/) {
930                 my $remote_ip = &get_local_ip_for_remote_ip(sprintf("%s", $address =~ /^([0-9\.]*?):.*$/));
931                 $msg =~ s/<source>(0\.0\.0\.0):(\d*?)<\/source>/<source>$remote_ip:$2<\/source>/s;
932         }
934     # encrypt xml msg
935     my $crypted_msg = &encrypt_msg($msg, $encrypt_key);
937     # opensocket
938     my $socket = &open_socket($address);
939     if( !$socket ) {
940         daemon_log("$session_id WARNING: cannot send ".$header."msg to $address , host not reachable", 3);
941         $error++;
942     }
943     
944     if( $error == 0 ) {
945         # send xml msg
946         print $socket $crypted_msg."\n";
948         daemon_log("$session_id INFO: send ".$header."msg to $address", 5);
949         daemon_log("$session_id DEBUG: message:\n$msg", 9);
950         
951     }
953     # close socket in any case
954     if( $socket ) {
955         close $socket;
956     }
958     if( $error > 0 ) { $new_status = "down"; }
959     else { $new_status = $msg_header; }
962     # known_clients
963     $sql_statement = "SELECT * FROM $known_clients_tn WHERE hostname='$address'";
964     $res = $known_clients_db->select_dbentry($sql_statement);
965     if( keys(%$res) == 1) {
966         $act_status = exists $res->{1}->{'status'} ? $res->{1}->{'status'} : "";
967         if ($act_status eq "down" && $new_status eq "down") {
968             $sql_statement = "DELETE FROM known_clients WHERE hostname='$address'";
969             $res = $known_clients_db->del_dbentry($sql_statement);
970             daemon_log("$session_id WARNING: failed 2x to send msg to host '$address', delete host from known_clients", 3);
971         } else { 
972             $sql_statement = "UPDATE known_clients SET status='$new_status', timestamp='$timestamp' WHERE hostname='$address'";
973             $res = $known_clients_db->update_dbentry($sql_statement);
974             if($new_status eq "down"){
975                 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
976             } else {
977                 daemon_log("$session_id INFO: set '$address' from status '$act_status' to '$new_status'", 5);
978             }
979         }
980     }
982     # known_server
983     $sql_statement = "SELECT * FROM $known_server_tn WHERE hostname='$address'";
984     $res = $known_server_db->select_dbentry($sql_statement);
985     if( keys(%$res) == 1) {
986         $act_status = exists $res->{1}->{'status'} ? $res->{1}->{'status'} : "";
987         if ($act_status eq "down" && $new_status eq "down") {
988             $sql_statement = "DELETE FROM known_server WHERE hostname='$address'";
989             $res = $known_server_db->del_dbentry($sql_statement);
990             daemon_log("$session_id WARNING: failed 2x to send a message to host '$address', delete host from known_server", 3);
991         } 
992         else { 
993             $sql_statement = "UPDATE known_server SET status='$new_status', timestamp='$timestamp' WHERE hostname='$address'";
994             $res = $known_server_db->update_dbentry($sql_statement);
995             if($new_status eq "down"){
996                 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
997             } else {
998                 daemon_log("$session_id INFO: set '$address' from status '$act_status' to '$new_status'", 5);
999             }
1000         }
1001     }
1002     return $error; 
1006 sub update_jobdb_status_for_send_msgs {
1007     my ($answer, $error) = @_;
1008     if( $answer =~ /<jobdb_id>(\d+)<\/jobdb_id>/ ) {
1009         my $jobdb_id = $1;
1010             
1011         # sending msg faild
1012         if( $error ) {
1013             if (not $answer =~ /<header>trigger_action_reinstall<\/header>/) {
1014                 my $sql_statement = "UPDATE $job_queue_tn ".
1015                     "SET status='error', result='can not deliver msg, please consult log file' ".
1016                     "WHERE id=$jobdb_id";
1017                 my $res = $job_db->update_dbentry($sql_statement);
1018             }
1020         # sending msg was successful
1021         } else {
1022             my $sql_statement = "UPDATE $job_queue_tn ".
1023                 "SET status='done' ".
1024                 "WHERE id=$jobdb_id AND status='processed'";
1025             my $res = $job_db->update_dbentry($sql_statement);
1026         }
1027     }
1031 sub sig_handler {
1032         my ($kernel, $signal) = @_[KERNEL, ARG0] ;
1033         daemon_log("0 INFO got signal '$signal'", 1); 
1034         $kernel->sig_handled();
1035         return;
1039 sub msg_to_decrypt {
1040         my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
1041         my $session_id = $session->ID;
1042         my ($msg, $msg_hash, $module);
1043         my $error = 0;
1045         # hole neue msg aus @msgs_to_decrypt
1046         my $next_msg = shift @msgs_to_decrypt;
1048         # msg is from a new client or gosa
1049         ($msg, $msg_hash, $module) = &input_from_unknown_host($next_msg, $session_id);
1051         # msg is from a gosa-si-server
1052         if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1053                 ($msg, $msg_hash, $module) = &input_from_known_server($next_msg, $heap->{'remote_ip'}, $session_id);
1054         }
1055         # msg is from a gosa-si-client
1056         if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1057                 ($msg, $msg_hash, $module) = &input_from_known_client($next_msg, $heap->{'remote_ip'}, $session_id);
1058         }
1059         # an error occurred
1060         if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1061                 # if an incoming msg could not be decrypted (maybe a wrong key), send client a ping. If the client
1062                 # could not understand a msg from its server the client cause a re-registering process
1063                 daemon_log("$session_id WARNING cannot understand incoming msg, send 'ping'-msg to all host with ip '".$heap->{remote_ip}.
1064                         "' to cause a re-registering of the client if necessary", 3);
1065                 my $sql_statement = "SELECT * FROM $main::known_clients_tn WHERE (hostname LIKE '".$heap->{'remote_ip'}."%')";
1066                 my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
1067                 while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1068                         my $host_name = $hit->{'hostname'};
1069                         my $host_key = $hit->{'hostkey'};
1070                         my $ping_msg = "<xml> <header>gosa_ping</header> <source>$server_address</source> <target>$host_name</target></xml>";
1071                         my $error = &send_msg_to_target($ping_msg, $host_name, $host_key, "gosa_ping", $session_id);
1072                         &update_jobdb_status_for_send_msgs($ping_msg, $error);
1073                 }
1074                 $error++;
1075         }
1078         my $header;
1079         my $target;
1080         my $source;
1081         my $done = 0;
1082         my $sql;
1083         my $res;
1085         # check whether this message should be processed here
1086         if ($error == 0) {
1087                 $header = @{$msg_hash->{'header'}}[0];
1088                 $target = @{$msg_hash->{'target'}}[0];
1089                 $source = @{$msg_hash->{'source'}}[0];
1090                 my $not_found_in_known_clients_db = 0;
1091                 my $not_found_in_known_server_db = 0;
1092                 my $not_found_in_foreign_clients_db = 0;
1093                 my $local_address;
1094                 my $local_mac;
1095                 my ($target_ip, $target_port) = split(':', $target);
1097                 # Determine the local ip address if target is an ip address
1098                 if ($target =~ /^\d+\.\d+\.\d+\.\d+:\d+$/) {
1099                         $local_address = &get_local_ip_for_remote_ip($target_ip).":$server_port";
1100                 } else {
1101                         $local_address = $server_address;
1102                 }
1104                 # Determine the local mac address if target is a mac address
1105                 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) {
1106                         my $loc_ip = &get_local_ip_for_remote_ip($heap->{'remote_ip'});
1107                         my $network_interface= &get_interface_for_ip($loc_ip);
1108                         $local_mac = &get_mac_for_interface($network_interface);
1109                 } else {
1110                         $local_mac = $server_mac_address;
1111                 }
1113                 # target and source is equal to GOSA -> process here
1114                 if (not $done) {
1115                         if ($target eq "GOSA" && $source eq "GOSA") {
1116                                 $done = 1;                    
1117                                 &daemon_log("$session_id DEBUG: target and source is 'GOSA' -> process here", 7);
1118                         }
1119                 }
1121                 # target is own address without forward_to_gosa-tag -> process here
1122                 if (not $done) {
1123                         #if ((($target eq $local_address) || ($target eq $local_mac) ) && (not exists $msg_hash->{'forward_to_gosa'})) {
1124                         if (($target eq $local_address) && (not exists $msg_hash->{'forward_to_gosa'})) {
1125                                 $done = 1;
1126                                 if ($source eq "GOSA") {
1127                                         $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1128                                 }
1129                                 &daemon_log("$session_id DEBUG: target is own address without forward_to_gosa-tag -> process here", 7);
1130                         }
1131                 }
1133                 # target is a client address in known_clients -> process here
1134                 if (not $done) {
1135                         $sql = "SELECT * FROM $known_clients_tn WHERE (hostname='$target' OR macaddress LIKE '$target')"; 
1136                         $res = $known_clients_db->select_dbentry($sql);
1137                         if (keys(%$res) > 0) {
1138                                 $done = 1; 
1139                                 my $hostname = $res->{1}->{'hostname'};
1140                                 $msg =~ s/<target>$target<\/target>/<target>$hostname<\/target>/;
1141                                 my $local_address = &get_local_ip_for_remote_ip($target_ip).":$server_port";
1142                                 if ($source eq "GOSA") {
1143                                         $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1144                                 }
1145                                 &daemon_log("$session_id DEBUG: target is a client address in known_clients -> process here", 7);
1147                         } else {
1148                                 $not_found_in_known_clients_db = 1;
1149                         }
1150                 }
1152                 # target ist own address with forward_to_gosa-tag not pointing to myself -> process here
1153                 if (not $done) {
1154                         my $forward_to_gosa =  @{$msg_hash->{'forward_to_gosa'}}[0];
1155                         my $gosa_at;
1156                         my $gosa_session_id;
1157                         if (($target eq $local_address) && (defined $forward_to_gosa)){
1158                                 my ($gosa_at, $gosa_session_id) = split(/,/, $forward_to_gosa);
1159                                 if ($gosa_at ne $local_address) {
1160                                         $done = 1;
1161                                         &daemon_log("$session_id DEBUG: target is own address with forward_to_gosa-tag not pointing to myself -> process here", 7); 
1162                                 }
1163                         }
1164                 }
1166                 # if message should be processed here -> add message to incoming_db
1167                 if ($done) {
1168                         # if a job or a gosa message comes from a foreign server, fake module to GosaPackages
1169                         # so gosa-si-server knows how to process this kind of messages
1170                         if ($header =~ /^gosa_/ || $header =~ /^job_/) {
1171                                 $module = "GosaPackages";
1172                         }
1174                         my $res = $incoming_db->add_dbentry( {table=>$incoming_tn,
1175                                         primkey=>[],
1176                                         headertag=>$header,
1177                                         targettag=>$target,
1178                                         xmlmessage=>&encode_base64($msg),
1179                                         timestamp=>&get_time,
1180                                         module=>$module,
1181                                         sessionid=>$session_id,
1182                                 } );
1184                 }
1186                 # target is own address with forward_to_gosa-tag pointing at myself -> forward to gosa
1187                 if (not $done) {
1188                         my $forward_to_gosa =  @{$msg_hash->{'forward_to_gosa'}}[0];
1189                         my $gosa_at;
1190                         my $gosa_session_id;
1191                         if (($target eq $local_address) && (defined $forward_to_gosa)){
1192                                 my ($gosa_at, $gosa_session_id) = split(/,/, $forward_to_gosa);
1193                                 if ($gosa_at eq $local_address) {
1194                                         my $session_reference = $kernel->ID_id_to_session($gosa_session_id);
1195                                         if( defined $session_reference ) {
1196                                                 $heap = $session_reference->get_heap();
1197                                         }
1198                                         if(exists $heap->{'client'}) {
1199                                                 $msg = &encrypt_msg($msg, $GosaPackages_key);
1200                                                 $heap->{'client'}->put($msg);
1201                                                 &daemon_log("$session_id INFO: incoming '$header' message forwarded to GOsa", 5); 
1202                                         }
1203                                         $done = 1;
1204                                         &daemon_log("$session_id DEBUG: target is own address with forward_to_gosa-tag pointing at myself -> forward to gosa", 7);
1205                                 }
1206                         }
1208                 }
1210                 # target is a client address in foreign_clients -> forward to registration server
1211                 if (not $done) {
1212                         $sql = "SELECT * FROM $foreign_clients_tn WHERE (hostname='$target' OR macaddress LIKE '$target')";
1213                         $res = $foreign_clients_db->select_dbentry($sql);
1214                         if (keys(%$res) > 0) {
1215                                 my $hostname = $res->{1}->{'hostname'};
1216                                 my ($host_ip, $host_port) = split(/:/, $hostname);
1217                                 my $local_address =  &get_local_ip_for_remote_ip($host_ip).":$server_port";
1218                                 my $regserver = $res->{1}->{'regserver'};
1219                                 my $sql = "SELECT * FROM $known_server_tn WHERE hostname='$regserver'"; 
1220                                 my $res = $known_server_db->select_dbentry($sql);
1221                                 if (keys(%$res) > 0) {
1222                                         my $regserver_key = $res->{1}->{'hostkey'};
1223                                         $msg =~ s/<source>GOSA<\/source>/<source>$local_address<\/source>/;
1224                                         $msg =~ s/<target>$target<\/target>/<target>$hostname<\/target>/;
1225                                         if ($source eq "GOSA") {
1226                                                 $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1227                                         }
1228                                         &send_msg_to_target($msg, $regserver, $regserver_key, $header, $session_id);
1229                                 }
1230                                 $done = 1;
1231                                 &daemon_log("$session_id DEBUG: target is a client address in foreign_clients -> forward to registration server", 7);
1232                         } else {
1233                                 $not_found_in_foreign_clients_db = 1;
1234                         }
1235                 }
1237                 # target is a server address -> forward to server
1238                 if (not $done) {
1239                         $sql = "SELECT * FROM $known_server_tn WHERE (hostname='$target' OR macaddress LIKE '$target')";
1240                         $res = $known_server_db->select_dbentry($sql);
1241                         if (keys(%$res) > 0) {
1242                                 my $hostkey = $res->{1}->{'hostkey'};
1244                                 if ($source eq "GOSA") {
1245                                         $msg =~ s/<source>GOSA<\/source>/<source>$local_address<\/source>/;
1246                                         $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1248                                 }
1250                                 &send_msg_to_target($msg, $target, $hostkey, $header, $session_id);
1251                                 $done = 1;
1252                                 &daemon_log("$session_id DEBUG: target is a server address -> forward to server", 7);
1253                         } else {
1254                                 $not_found_in_known_server_db = 1;
1255                         }
1256                 }
1259                 # target is not in foreign_clients_db, known_server_db or known_clients_db, maybe it is a complete new one -> process here
1260                 if ( $not_found_in_foreign_clients_db 
1261                         && $not_found_in_known_server_db
1262                         && $not_found_in_known_clients_db) {
1263                         &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);
1264                         my $res = $incoming_db->add_dbentry( {table=>$incoming_tn,
1265                                         primkey=>[],
1266                                         headertag=>$header,
1267                                         targettag=>$target,
1268                                         xmlmessage=>&encode_base64($msg),
1269                                         timestamp=>&get_time,
1270                                         module=>$module,
1271                                         sessionid=>$session_id,
1272                                 } );
1273                         $done = 1;
1274                 }
1277                 if (not $done) {
1278                         daemon_log("$session_id ERROR: do not know what to do with this message: $msg", 1);
1279                         if ($source eq "GOSA") {
1280                                 my %data = ('error_msg' => &encode_base64($msg), 'error_string' => "Do not know what to do with this message!");
1281                                 my $error_msg = &build_msg("error", $local_address, "GOSA", \%data ); 
1283                                 my $session_reference = $kernel->ID_id_to_session($session_id);
1284                                 if( defined $session_reference ) {
1285                                         $heap = $session_reference->get_heap();
1286                                 }
1287                                 if(exists $heap->{'client'}) {
1288                                         $error_msg = &encrypt_msg($error_msg, $GosaPackages_key);
1289                                         $heap->{'client'}->put($error_msg);
1290                                 }
1291                         }
1292                 }
1294         }
1296         return;
1300 sub next_task {
1301     my ($session, $heap, $task) = @_[SESSION, HEAP, ARG0];
1302     my $running_task = POE::Wheel::Run->new(
1303             Program => sub { process_task($session, $heap, $task) },
1304             StdioFilter => POE::Filter::Reference->new(),
1305             StdoutEvent  => "task_result",
1306             StderrEvent  => "task_debug",
1307             CloseEvent   => "task_done",
1308             );
1309     $heap->{task}->{ $running_task->ID } = $running_task;
1312 sub handle_task_result {
1313     my ($kernel, $heap, $result) = @_[KERNEL, HEAP, ARG0];
1314     my $client_answer = $result->{'answer'};
1315     if( $client_answer =~ s/session_id=(\d+)$// ) {
1316         my $session_id = $1;
1317         if( defined $session_id ) {
1318             my $session_reference = $kernel->ID_id_to_session($session_id);
1319             if( defined $session_reference ) {
1320                 $heap = $session_reference->get_heap();
1321             }
1322         }
1324         if(exists $heap->{'client'}) {
1325             $heap->{'client'}->put($client_answer);
1326         }
1327     }
1328     $kernel->sig(CHLD => "child_reap");
1331 sub handle_task_debug {
1332     my $result = $_[ARG0];
1333     print STDERR "$result\n";
1336 sub handle_task_done {
1337     my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
1338     delete $heap->{task}->{$task_id};
1341 sub process_task {
1342     no strict "refs";
1343     #CHECK: Not @_[...]?
1344     my ($session, $heap, $task) = @_;
1345     my $error = 0;
1346     my $answer_l;
1347     my ($answer_header, @answer_target_l, $answer_source);
1348     my $client_answer = "";
1350     # prepare all variables needed to process message
1351     #my $msg = $task->{'xmlmessage'};
1352     my $msg = &decode_base64($task->{'xmlmessage'});
1353     my $incoming_id = $task->{'id'};
1354     my $module = $task->{'module'};
1355     my $header =  $task->{'headertag'};
1356     my $session_id = $task->{'sessionid'};
1357                 my $msg_hash;
1358                 eval {
1359         $msg_hash = $xml->XMLin($msg, ForceArray=>1);
1360                 }; 
1361                 daemon_log("ERROR: XML failure '$@'") if ($@);
1362     my $source = @{$msg_hash->{'source'}}[0];
1363     
1364     # set timestamp of incoming client uptodate, so client will not 
1365     # be deleted from known_clients because of expiration
1366     my $act_time = &get_time();
1367     my $sql = "UPDATE $known_clients_tn SET timestamp='$act_time' WHERE hostname='$source'"; 
1368     my $res = $known_clients_db->exec_statement($sql);
1370     ######################
1371     # process incoming msg
1372     if( $error == 0) {
1373         daemon_log("$session_id INFO: Incoming msg (session_id=$session_id) with header '".@{$msg_hash->{'header'}}[0]."'", 5); 
1374         daemon_log("$session_id DEBUG: Processing module ".$module, 7);
1375         $answer_l = &{ $module."::process_incoming_msg" }($msg, $msg_hash, $session_id);
1377         if ( 0 < @{$answer_l} ) {
1378             my $answer_str = join("\n", @{$answer_l});
1379             while ($answer_str =~ /<header>(\w+)<\/header>/g) {
1380                 daemon_log("$session_id INFO: got answer message with header '$1'", 5);
1381             }
1382             daemon_log("$session_id DEBUG: $module: got answer from module: \n".$answer_str,9);
1383         } else {
1384             daemon_log("$session_id DEBUG: $module: got no answer from module!" ,7);
1385         }
1387     }
1388     if( !$answer_l ) { $error++ };
1390     ########
1391     # answer
1392     if( $error == 0 ) {
1394         foreach my $answer ( @{$answer_l} ) {
1395             # check outgoing msg to xml validity
1396             my $answer_hash = &check_outgoing_xml_validity($answer, $session_id);
1397             if( not defined $answer_hash ) { next; }
1398             
1399             $answer_header = @{$answer_hash->{'header'}}[0];
1400             @answer_target_l = @{$answer_hash->{'target'}};
1401             $answer_source = @{$answer_hash->{'source'}}[0];
1403             # deliver msg to all targets 
1404             foreach my $answer_target ( @answer_target_l ) {
1406                 # targets of msg are all gosa-si-clients in known_clients_db
1407                 if( $answer_target eq "*" ) {
1408                     # answer is for all clients
1409                     my $sql_statement= "SELECT * FROM known_clients";
1410                     my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
1411                     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1412                         my $host_name = $hit->{hostname};
1413                         my $host_key = $hit->{hostkey};
1414                         my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1415                         &update_jobdb_status_for_send_msgs($answer, $error);
1416                     }
1417                 }
1419                 # targets of msg are all gosa-si-server in known_server_db
1420                 elsif( $answer_target eq "KNOWN_SERVER" ) {
1421                     # answer is for all server in known_server
1422                     my $sql_statement= "SELECT * FROM $known_server_tn";
1423                     my $query_res = $known_server_db->select_dbentry( $sql_statement ); 
1424                     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1425                         my $host_name = $hit->{hostname};
1426                         my $host_key = $hit->{hostkey};
1427                         $answer =~ s/<target>\S+<\/target>/<target>$host_name<\/target>/g;
1428                         my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1429                         &update_jobdb_status_for_send_msgs($answer, $error);
1430                     }
1431                 }
1433                 # target of msg is GOsa
1434                                 elsif( $answer_target eq "GOSA" ) {
1435                                         my $session_id = ($1) if $answer =~ /<session_id>(\d+?)<\/session_id>/;
1436                                         my $add_on = "";
1437                     if( defined $session_id ) {
1438                         $add_on = ".session_id=$session_id";
1439                     }
1440                     # answer is for GOSA and has to returned to connected client
1441                     my $gosa_answer = &encrypt_msg($answer, $GosaPackages_key);
1442                     $client_answer = $gosa_answer.$add_on;
1443                 }
1445                 # target of msg is job queue at this host
1446                 elsif( $answer_target eq "JOBDB") {
1447                     $answer =~ /<header>(\S+)<\/header>/;   
1448                     my $header;
1449                     if( defined $1 ) { $header = $1; }
1450                     my $error = &send_msg_to_target($answer, $server_address, $GosaPackages_key, $header, $session_id);
1451                     &update_jobdb_status_for_send_msgs($answer, $error);
1452                 }
1454                 # Target of msg is a mac address
1455                 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 ) {
1456                     daemon_log("$session_id INFO: target is mac address '$answer_target', looking for host in known_clients and foreign_clients", 5);
1457                     my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$answer_target'";
1458                     my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1459                     my $found_ip_flag = 0;
1460                     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1461                         my $host_name = $hit->{hostname};
1462                         my $host_key = $hit->{hostkey};
1463                         $answer =~ s/$answer_target/$host_name/g;
1464                         daemon_log("$session_id INFO: found host '$host_name', associated to '$answer_target'", 5);
1465                         my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1466                         &update_jobdb_status_for_send_msgs($answer, $error);
1467                         $found_ip_flag++ ;
1468                     }   
1469                     if ($found_ip_flag == 0) {
1470                         my $sql = "SELECT * FROM $foreign_clients_tn WHERE macaddress LIKE '$answer_target'";
1471                         my $res = $foreign_clients_db->select_dbentry($sql);
1472                         while( my ($hit_num, $hit) = each %{ $res } ) {
1473                             my $host_name = $hit->{hostname};
1474                             my $reg_server = $hit->{regserver};
1475                             daemon_log("$session_id INFO: found host '$host_name' with mac '$answer_target', registered at '$reg_server'", 5);
1476                             
1477                             # Fetch key for reg_server
1478                             my $reg_server_key;
1479                             my $sql = "SELECT * FROM $known_server_tn WHERE hostname='$reg_server'";
1480                             my $res = $known_server_db->select_dbentry($sql);
1481                             if (exists $res->{1}) {
1482                                 $reg_server_key = $res->{1}->{'hostkey'}; 
1483                             } else {
1484                                 daemon_log("$session_id ERROR: cannot find hostkey for '$host_name' in '$known_server_tn'", 1); 
1485                                 daemon_log("$session_id ERROR: unable to forward answer to correct registration server, processing is aborted!", 1); 
1486                                 $reg_server_key = undef;
1487                             }
1489                             # Send answer to server where client is registered
1490                             if (defined $reg_server_key) {
1491                                 $answer =~ s/$answer_target/$host_name/g;
1492                                 my $error = &send_msg_to_target($answer, $reg_server, $reg_server_key, $answer_header, $session_id);
1493                                 &update_jobdb_status_for_send_msgs($answer, $error);
1494                                 $found_ip_flag++ ;
1495                             }
1496                         }
1497                     }
1498                     if( $found_ip_flag == 0) {
1499                         daemon_log("$session_id WARNING: no host found in known_clients or foreign_clients with mac address '$answer_target'", 3);
1501                         # Sometimes the client is still booting or does not wake up, in this case reactivate the job (if it exists) with a delay of 30 sec
1502                         my $delay_timestamp = &calc_timestamp(&get_time(), "plus", 30);
1503                         my $sql = "UPDATE $job_queue_tn Set timestamp='$delay_timestamp', status='waiting' WHERE (macaddress='$answer_target' AND headertag='$answer_header')"; 
1504                         my $res = $job_db->update_dbentry($sql);
1505                         daemon_log("$session_id INFO: '$answer_header'-job will be reactivated at '$delay_timestamp' ".
1506                                 "cause client '$answer_target' is currently not available", 5);
1507                         daemon_log("$session_id $sql", 7);                                
1508                     }
1510                 # Answer is for one specific host   
1511                 } else {
1512                     # get encrypt_key
1513                     my $encrypt_key = &get_encrypt_key($answer_target);
1514                     if( not defined $encrypt_key ) {
1515                         # unknown target
1516                         daemon_log("$session_id WARNING: unknown target '$answer_target'", 3);
1517                         next;
1518                     }
1519                     my $error = &send_msg_to_target($answer, $answer_target, $encrypt_key, $answer_header,$session_id);
1520                     &update_jobdb_status_for_send_msgs($answer, $error);
1521                 }
1522             }
1523         }
1524     }
1526     my $filter = POE::Filter::Reference->new();
1527     my %result = ( 
1528             status => "seems ok to me",
1529             answer => $client_answer,
1530             );
1532     my $output = $filter->put( [ \%result ] );
1533     print @$output;
1538 sub session_start {
1539     my ($kernel) = $_[KERNEL];
1540     $global_kernel = $kernel;
1541     $kernel->yield('register_at_foreign_servers');
1542         $kernel->yield('create_fai_server_db', $fai_server_tn );
1543         $kernel->yield('create_fai_release_db', $fai_release_tn );
1544     $kernel->yield('watch_for_next_tasks');
1545         $kernel->sig(USR1 => "sig_handler");
1546         $kernel->sig(USR2 => "recreate_packages_db");
1547         $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1548         $kernel->delay_set('watch_for_done_jobs', $job_queue_loop_delay); 
1549     $kernel->delay_set('watch_for_modified_jobs', $modified_jobs_loop_delay); 
1550         $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay);
1551     $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay);
1552         $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay);
1553     $kernel->delay_set('watch_for_old_known_clients', $job_queue_loop_delay);
1555     # Start opsi check
1556     if ($opsi_enabled eq "true") {
1557         $kernel->delay_set('watch_for_opsi_jobs', $job_queue_opsi_delay); 
1558     }
1563 sub watch_for_done_jobs {
1564     #CHECK: $heap for what?
1565     my ($kernel,$heap) = @_[KERNEL, HEAP];
1567     my $sql_statement = "SELECT * FROM ".$job_queue_tn." WHERE ((status='done') AND (modified='0'))";
1568         my $res = $job_db->select_dbentry( $sql_statement );
1570     while( my ($id, $hit) = each %{$res} ) {
1571         my $jobdb_id = $hit->{id};
1572         my $sql_statement = "DELETE FROM $job_queue_tn WHERE id=$jobdb_id"; 
1573         my $res = $job_db->del_dbentry($sql_statement); 
1574     }
1576     $kernel->delay_set('watch_for_done_jobs',$job_queue_loop_delay);
1580 sub watch_for_opsi_jobs {
1581     my ($kernel) = $_[KERNEL];
1583     # 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 
1584     # opsi install job is to parse the xml message. There is still the correct header.
1585     my $sql_statement = "SELECT * FROM ".$job_queue_tn." WHERE ((xmlmessage LIKE '%opsi_install_client</header>%') AND (status='processing') AND (siserver='localhost'))";
1586         my $res = $job_db->select_dbentry( $sql_statement );
1588     # Ask OPSI for an update of the running jobs
1589     while (my ($id, $hit) = each %$res ) {
1590         # Determine current parameters of the job
1591         my $hostId = $hit->{'plainname'};
1592         my $macaddress = $hit->{'macaddress'};
1593         my $progress = $hit->{'progress'};
1595         my $result= {};
1596         
1597         # For hosts, only return the products that are or get installed
1598         my $callobj;
1599         $callobj = {
1600             method  => 'getProductStates_hash',
1601             params  => [ $hostId ],
1602             id  => 1,
1603         };
1604         
1605         my $hres = $opsi_client->call($opsi_url, $callobj);
1606         #my ($hres_err, $hres_err_string) = &check_opsi_res($hres);
1607         if (not &check_opsi_res($hres)) {
1608             my $htmp= $hres->result->{$hostId};
1609         
1610             # Check state != not_installed or action == setup -> load and add
1611             my $products= 0;
1612             my $installed= 0;
1613             my $installing = 0;
1614             my $error= 0;  
1615             my @installed_list;
1616             my @error_list;
1617             my $act_status = "none";
1618             foreach my $product (@{$htmp}){
1620                 if ($product->{'installationStatus'} ne "not_installed" or
1621                         $product->{'actionRequest'} eq "setup"){
1623                     # Increase number of products for this host
1624                     $products++;
1625         
1626                     if ($product->{'installationStatus'} eq "failed"){
1627                         $result->{$product->{'productId'}}= "error";
1628                         unshift(@error_list, $product->{'productId'});
1629                         $error++;
1630                     }
1631                     if ($product->{'installationStatus'} eq "installed" && $product->{'actionRequest'}  eq "none"){
1632                         $result->{$product->{'productId'}}= "installed";
1633                         unshift(@installed_list, $product->{'productId'});
1634                         $installed++;
1635                     }
1636                     if ($product->{'installationStatus'} eq "installing"){
1637                         $result->{$product->{'productId'}}= "installing";
1638                         $installing++;
1639                         $act_status = "installing - ".$product->{'productId'};
1640                     }
1641                 }
1642             }
1643         
1644             # Estimate "rough" progress, avoid division by zero
1645             if ($products == 0) {
1646                 $result->{'progress'}= 0;
1647             } else {
1648                 $result->{'progress'}= int($installed * 100 / $products);
1649             }
1651             # Set updates in job queue
1652             if ((not $error) && (not $installing) && ($installed)) {
1653                 $act_status = "installed - ".join(", ", @installed_list);
1654             }
1655             if ($error) {
1656                 $act_status = "error - ".join(", ", @error_list);
1657             }
1658             if ($progress ne $result->{'progress'} ) {
1659                 # Updating progress and result 
1660                 my $update_statement = "UPDATE $job_queue_tn SET modified='1', progress='".$result->{'progress'}."', result='$act_status' WHERE macaddress='$macaddress' AND siserver='localhost'";
1661                 my $update_res = $job_db->update_dbentry($update_statement);
1662             }
1663             if ($progress eq 100) { 
1664                 # Updateing status
1665                 my $done_statement = "UPDATE $job_queue_tn SET modified='1', ";
1666                 if ($error) {
1667                     $done_statement .= "status='error'";
1668                 } else {
1669                     $done_statement .= "status='done'";
1670                 }
1671                 $done_statement .= " WHERE macaddress='$macaddress' AND siserver='localhost'";
1672                 my $done_res = $job_db->update_dbentry($done_statement);
1673             }
1676         }
1677     }
1679     $kernel->delay_set('watch_for_opsi_jobs', $job_queue_opsi_delay);
1683 # If a job got an update or was modified anyway, send to all other si-server an update message of this jobs.
1684 sub watch_for_modified_jobs {
1685     my ($kernel,$heap) = @_[KERNEL, HEAP];
1687     my $sql_statement = "SELECT * FROM $job_queue_tn WHERE ((siserver='localhost') AND (modified='1'))"; 
1688     my $res = $job_db->select_dbentry( $sql_statement );
1689     
1690     # if db contains no jobs which should be update, do nothing
1691     if (keys %$res != 0) {
1693         if ($job_synchronization  eq "true") {
1694             # make out of the db result a gosa-si message   
1695             my $update_msg = &db_res2si_msg ($res, "foreign_job_updates", "KNOWN_SERVER", "MY_LOCAL_ADDRESS");
1696  
1697             # update all other SI-server
1698             &inform_all_other_si_server($update_msg);
1699         }
1701         # set jobs all jobs to modified = 0, wait until the next modification for updates of other si-server
1702         $sql_statement = "UPDATE $job_queue_tn SET modified='0' ";
1703         $res = $job_db->update_dbentry($sql_statement);
1704     }
1706     $kernel->delay_set('watch_for_modified_jobs', $modified_jobs_loop_delay);
1710 sub watch_for_new_jobs {
1711         if($watch_for_new_jobs_in_progress == 0) {
1712                 $watch_for_new_jobs_in_progress = 1;
1713                 my ($kernel,$heap) = @_[KERNEL, HEAP];
1715                 # check gosa job quaeue for jobs with executable timestamp
1716                 my $timestamp = &get_time();
1717                 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE status='waiting' AND (CAST(timestamp AS UNSIGNED)) < $timestamp ORDER BY timestamp";
1718                 my $res = $job_db->exec_statement( $sql_statement );
1720                 # Merge all new jobs that would do the same actions
1721                 my @drops;
1722                 my $hits;
1723                 foreach my $hit (reverse @{$res} ) {
1724                         my $macaddress= lc @{$hit}[8];
1725                         my $headertag= @{$hit}[5];
1726                         if(
1727                                 defined($hits->{$macaddress}) &&
1728                                 defined($hits->{$macaddress}->{$headertag}) &&
1729                                 defined($hits->{$macaddress}->{$headertag}[0])
1730                         ) {
1731                                 push @drops, "DELETE FROM $job_queue_tn WHERE id = $hits->{$macaddress}->{$headertag}[0]";
1732                         }
1733                         $hits->{$macaddress}->{$headertag}= $hit;
1734                 }
1736                 # Delete new jobs with a matching job in state 'processing'
1737                 foreach my $macaddress (keys %{$hits}) {
1738                         foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1739                                 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1740                                 if(defined($jobdb_id)) {
1741                                         my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND headertag='$jobdb_headertag' AND status='processing'";
1742                                         my $res = $job_db->exec_statement( $sql_statement );
1743                                         foreach my $hit (@{$res}) {
1744                                                 push @drops, "DELETE FROM $job_queue_tn WHERE id=$jobdb_id";
1745                                         }
1746                                 } else {
1747                                         daemon_log("J ERROR: Job without id exists for macaddress $macaddress!", 1);
1748                                 }
1749                         }
1750                 }
1752                 # Commit deletion
1753                 $job_db->exec_statementlist(\@drops);
1755                 # Look for new jobs that could be executed
1756                 foreach my $macaddress (keys %{$hits}) {
1758                         # Look if there is an executing job
1759                         my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND status='processing'";
1760                         my $res = $job_db->exec_statement( $sql_statement );
1762                         # Skip new jobs for host if there is a processing job
1763                         if(defined($res) and defined @{$res}[0]) {
1764                                 # Prevent race condition if there is a trigger_activate job waiting and a goto-activation job processing
1765                                 my $row = @{$res}[0] if (ref $res eq 'ARRAY');
1766                                 if(@{$row}[5] eq 'trigger_action_reinstall') {
1767                                         my $sql_statement_2 =  "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND status='waiting' AND headertag = 'trigger_activate_new'"; 
1768                                         my $res_2 = $job_db->exec_statement( $sql_statement_2 );
1769                                         if(defined($res_2) and defined @{$res_2}[0]) {
1770                                                 # Set status from goto-activation to 'waiting' and update timestamp
1771                                                 $job_db->exec_statement("UPDATE $job_queue_tn SET status='waiting' WHERE macaddress LIKE '$macaddress' AND headertag = 'trigger_action_reinstall'");
1772                                                 $job_db->exec_statement("UPDATE $job_queue_tn SET timestamp='".&get_time(30)."' WHERE macaddress LIKE '$macaddress' AND headertag = 'trigger_action_reinstall'");
1773                                         }
1774                                 }
1775                                 next;
1776                         }
1778                         foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1779                                 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1780                                 if(defined($jobdb_id)) {
1781                                         my $job_msg = @{$hits->{$macaddress}->{$jobdb_headertag}}[7];
1783                                         daemon_log("J DEBUG: its time to execute $job_msg", 7);
1784                                         my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$macaddress'";
1785                                         my $res_hash = $known_clients_db->select_dbentry( $sql_statement );
1787                                         # expect macaddress is unique!!!!!!
1788                                         my $target = $res_hash->{1}->{hostname};
1790                                         # change header
1791                                         $job_msg =~ s/<header>job_/<header>gosa_/;
1793                                         # add sqlite_id
1794                                         $job_msg =~ s/<\/xml>$/<jobdb_id>$jobdb_id<\/jobdb_id><\/xml>/;
1796                                         $job_msg =~ /<header>(\S+)<\/header>/;
1797                                         my $header = $1 ;
1798                                         my $func_error = &send_msg_to_target($job_msg, $server_address, $GosaPackages_key, $header, "J");
1800                                         # update status in job queue to 'processing'
1801                                         $sql_statement = "UPDATE $job_queue_tn SET status='processing' WHERE id=$jobdb_id";
1802                                         my $res = $job_db->update_dbentry($sql_statement);
1803 # TODO: abfangen ob alles in ordnung ist oder nicht, wenn nicht error schmeißen                                        
1805                                         # We don't want parallel processing
1806                                         last;
1807                                 }
1808                         }
1809                 }
1811                 $watch_for_new_jobs_in_progress = 0;
1812                 $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1813         }
1817 sub watch_for_new_messages {
1818     my ($kernel,$heap) = @_[KERNEL, HEAP];
1819     my @coll_user_msg;   # collection list of outgoing messages
1820     
1821     # check messaging_db for new incoming messages with executable timestamp
1822     my $timestamp = &get_time();
1823     my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( (CAST(timestamp AS UNSIGNED))<$timestamp AND flag='n' AND direction='in' )";
1824     my $res = $messaging_db->exec_statement( $sql_statement );
1825         foreach my $hit (@{$res}) {
1827         # create outgoing messages
1828         my $message_to = @{$hit}[3];
1829         # translate message_to to plain login name
1830         my @message_to_l = split(/,/, $message_to);  
1831                 my %receiver_h; 
1832                 foreach my $receiver (@message_to_l) {
1833                         if ($receiver =~ /^u_([\s\S]*)$/) {
1834                                 $receiver_h{$1} = 0;
1835                         } elsif ($receiver =~ /^g_([\s\S]*)$/) {
1836                                 my $group_name = $1;
1837                                 # fetch all group members from ldap and add them to receiver hash
1838                                 my $ldap_handle = &get_ldap_handle();
1839                                 if (defined $ldap_handle) {
1840                                                 my $mesg = $ldap_handle->search(
1841                                                                                 base => $ldap_base,
1842                                                                                 scope => 'sub',
1843                                                                                 attrs => ['memberUid'],
1844                                                                                 filter => "cn=$group_name",
1845                                                                                 );
1846                                                 if ($mesg->count) {
1847                                                                 my @entries = $mesg->entries;
1848                                                                 foreach my $entry (@entries) {
1849                                                                                 my @receivers= $entry->get_value("memberUid");
1850                                                                                 foreach my $receiver (@receivers) { 
1851                                                                                                 $receiver_h{$1} = 0;
1852                                                                                 }
1853                                                                 }
1854                                                 } 
1855                                                 # translating errors ?
1856                                                 if ($mesg->code) {
1857                                                                 daemon_log("M ERROR: unable to translate group '$group_name' to user list for message delivery: $mesg->error", 1);
1858                                                 }
1859                                 # ldap handle error ?           
1860                                 } else {
1861                                         daemon_log("M ERROR: unable to translate group '$group_name' to user list for message delivery: no ldap handle available", 1);
1862                                 }
1863                         } else {
1864                                 my $sbjct = &encode_base64(@{$hit}[1]);
1865                                 my $msg = &encode_base64(@{$hit}[7]);
1866                                 &daemon_log("M WARNING: unknown receiver '$receiver' for a user-message '$sbjct - $msg'", 3); 
1867                         }
1868                 }
1869                 my @receiver_l = keys(%receiver_h);
1871         my $message_id = @{$hit}[0];
1873         #add each outgoing msg to messaging_db
1874         my $receiver;
1875         foreach $receiver (@receiver_l) {
1876             my $sql_statement = "INSERT INTO $messaging_tn (id, subject, message_from, message_to, flag, direction, delivery_time, message, timestamp) ".
1877                 "VALUES ('".
1878                 $message_id."', '".    # id
1879                 @{$hit}[1]."', '".     # subject
1880                 @{$hit}[2]."', '".     # message_from
1881                 $receiver."', '".      # message_to
1882                 "none"."', '".         # flag
1883                 "out"."', '".          # direction
1884                 @{$hit}[6]."', '".     # delivery_time
1885                 @{$hit}[7]."', '".     # message
1886                 $timestamp."'".     # timestamp
1887                 ")";
1888             &daemon_log("M DEBUG: $sql_statement", 1);
1889             my $res = $messaging_db->exec_statement($sql_statement);
1890             &daemon_log("M INFO: message '".@{$hit}[0]."' is prepared for delivery to receiver '$receiver'", 5);
1891         }
1893         # set incoming message to flag d=deliverd
1894         $sql_statement = "UPDATE $messaging_tn SET flag='p' WHERE id='$message_id'"; 
1895         &daemon_log("M DEBUG: $sql_statement", 7);
1896         $res = $messaging_db->update_dbentry($sql_statement);
1897         &daemon_log("M INFO: message '$message_id' is set to flag 'p' (processed)", 5);
1898     }
1900     $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay); 
1901     return;
1904 sub watch_for_delivery_messages {
1905     my ($kernel, $heap) = @_[KERNEL, HEAP];
1907     # select outgoing messages
1908     my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( flag='p' AND direction='out' )";
1909     #&daemon_log("0 DEBUG: $sql", 7);
1910     my $res = $messaging_db->exec_statement( $sql_statement );
1911     
1912     # build out msg for each    usr
1913     foreach my $hit (@{$res}) {
1914         my $receiver = @{$hit}[3];
1915         my $msg_id = @{$hit}[0];
1916         my $subject = @{$hit}[1];
1917         my $message = @{$hit}[7];
1919         # resolve usr -> host where usr is logged in
1920         my $sql = "SELECT * FROM $login_users_tn WHERE (user='$receiver')"; 
1921         #&daemon_log("0 DEBUG: $sql", 7);
1922         my $res = $login_users_db->exec_statement($sql);
1924         # reciver is logged in nowhere
1925         if (not ref(@$res[0]) eq "ARRAY") { next; }    
1927                 my $send_succeed = 0;
1928                 foreach my $hit (@$res) {
1929                                 my $receiver_host = @$hit[0];
1930                 my $delivered2host = 0;
1931                                 &daemon_log("M DEBUG: user '$receiver' is logged in at host '$receiver_host'", 7);
1933                                 # Looking for host in know_clients_db 
1934                                 my $sql = "SELECT * FROM $known_clients_tn WHERE (hostname='$receiver_host')";
1935                                 my $res = $known_clients_db->exec_statement($sql);
1937                 # Host is known in known_clients_db
1938                 if (ref(@$res[0]) eq "ARRAY") {
1939                     my $receiver_key = @{@{$res}[0]}[2];
1940                     my %data = ('subject' => $subject, 'message' => $message, 'usr' => $receiver);
1941                     my $out_msg = &build_msg("usr_msg", $server_address, $receiver_host, \%data ); 
1942                     my $error = &send_msg_to_target($out_msg, $receiver_host, $receiver_key, "usr_msg", 0); 
1943                     if ($error == 0 ) {
1944                         $send_succeed++ ;
1945                         $delivered2host++ ;
1946                         &daemon_log("M DEBUG: send message for user '$receiver' to host '$receiver_host'", 7); 
1947                     } else {
1948                         &daemon_log("M DEBUG: cannot send message for user '$receiver' to host '$receiver_host'", 7); 
1949                     }
1950                 }
1951                 
1952                 # Message already send, do not need to do anything more, otherwise ...
1953                 if ($delivered2host) { next;}
1954     
1955                 # ...looking for host in foreign_clients_db
1956                 $sql = "SELECT * FROM $foreign_clients_tn WHERE (hostname='$receiver_host')";
1957                 $res = $foreign_clients_db->exec_statement($sql);
1958   
1959                                 # Host is known in foreign_clients_db 
1960                                 if (ref(@$res[0]) eq "ARRAY") { 
1961                     my $registration_server = @{@{$res}[0]}[2];
1962                     
1963                     # Fetch encryption key for registration server
1964                     my $sql = "SELECT * FROM $known_server_tn WHERE (hostname='$registration_server')";
1965                     my $res = $known_server_db->exec_statement($sql);
1966                     if (ref(@$res[0]) eq "ARRAY") { 
1967                         my $registration_server_key = @{@{$res}[0]}[3];
1968                         my %data = ('subject' => $subject, 'message' => $message, 'usr' => $receiver);
1969                         my $out_msg = &build_msg("usr_msg", $server_address, $receiver_host, \%data ); 
1970                         my $error = &send_msg_to_target($out_msg, $registration_server, $registration_server_key, "usr_msg", 0); 
1971                         if ($error == 0 ) {
1972                             $send_succeed++ ;
1973                             $delivered2host++ ;
1974                             &daemon_log("M DEBUG: send message for user '$receiver' to server '$registration_server'", 7); 
1975                         } else {
1976                             &daemon_log("M ERROR: cannot send message for user '$receiver' to server '$registration_server'", 1); 
1977                         }
1979                     } else {
1980                         &daemon_log("M ERROR: host '$receiver_host' is reported to be ".
1981                                 "registrated at server '$registration_server', ".
1982                                 "but no data available in known_server_db ", 1); 
1983                     }
1984                 }
1985                 
1986                 if (not $delivered2host) {
1987                     &daemon_log("M ERROR: unable to send user message to host '$receiver_host'", 1);
1988                 }
1989                 }
1991                 if ($send_succeed) {
1992                                 # set outgoing msg at db to deliverd
1993                                 my $sql = "UPDATE $messaging_tn SET flag='d' WHERE (id='$msg_id' AND direction='out' AND message_to='$receiver')"; 
1994                                 my $res = $messaging_db->exec_statement($sql); 
1995                 &daemon_log("M INFO: send message for user '$receiver' to logged in hosts", 5);
1996                 } else {
1997             &daemon_log("M WARNING: failed to deliver message for user '$receiver'", 3); 
1998         }
1999         }
2001     $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay); 
2002     return;
2006 sub watch_for_done_messages {
2007     my ($kernel,$heap) = @_[KERNEL, HEAP];
2009     my $sql = "SELECT * FROM $messaging_tn WHERE (flag='p' AND direction='in')"; 
2010     #&daemon_log("0 DEBUG: $sql", 7);
2011     my $res = $messaging_db->exec_statement($sql); 
2013     foreach my $hit (@{$res}) {
2014         my $msg_id = @{$hit}[0];
2016         my $sql = "SELECT * FROM $messaging_tn WHERE (id='$msg_id' AND direction='out' AND (NOT flag='s'))"; 
2017         #&daemon_log("0 DEBUG: $sql", 7); 
2018         my $res = $messaging_db->exec_statement($sql);
2020         # not all usr msgs have been seen till now
2021         if ( ref(@$res[0]) eq "ARRAY") { next; }
2022         
2023         $sql = "DELETE FROM $messaging_tn WHERE (id='$msg_id')"; 
2024         #&daemon_log("0 DEBUG: $sql", 7);
2025         $res = $messaging_db->exec_statement($sql);
2026     
2027     }
2029     $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay); 
2030     return;
2034 sub watch_for_old_known_clients {
2035     my ($kernel,$heap) = @_[KERNEL, HEAP];
2037     my $sql_statement = "SELECT * FROM $known_clients_tn";
2038     my $res = $known_clients_db->select_dbentry( $sql_statement );
2040     my $act_time = int(&get_time());
2042     while ( my ($hit_num, $hit) = each %$res) {
2043         my $expired_timestamp = int($hit->{'timestamp'});
2044         $expired_timestamp =~ /(\d{4})(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)/;
2045         my $dt = DateTime->new( year   => $1,
2046                 month  => $2,
2047                 day    => $3,
2048                 hour   => $4,
2049                 minute => $5,
2050                 second => $6,
2051                 );
2053         $dt->add( seconds => 2 * int($hit->{'keylifetime'}) );
2054         $expired_timestamp = $dt->ymd('').$dt->hms('');
2055         if ($act_time > $expired_timestamp) {
2056             my $hostname = $hit->{'hostname'};
2057             my $del_sql = "DELETE FROM $known_clients_tn WHERE hostname='$hostname'"; 
2058             my $del_res = $known_clients_db->exec_statement($del_sql);
2060             &main::daemon_log("0 INFO: timestamp '".$hit->{'timestamp'}."' of client '$hostname' is expired('$expired_timestamp'), client will be deleted from known_clients_db", 5);
2061         }
2063     }
2065     $kernel->delay_set('watch_for_old_known_clients', $job_queue_loop_delay);
2069 sub watch_for_next_tasks {
2070     my ($kernel,$heap) = @_[KERNEL, HEAP];
2072     my $sql = "SELECT * FROM $incoming_tn";
2073     my $res = $incoming_db->select_dbentry($sql);
2074     
2075     while ( my ($hit_num, $hit) = each %$res) {
2076         my $headertag = $hit->{'headertag'};
2077         if ($headertag =~ /^answer_(\d+)/) {
2078             # do not start processing, this message is for a still running POE::Wheel
2079             next;
2080         }
2081         my $message_id = $hit->{'id'};
2082         my $session_id = $hit->{'sessionid'};
2083         &daemon_log("$session_id DEBUG: start processing for message with incoming id: '$message_id'", 7);
2084         $kernel->yield('next_task', $hit);
2086         my $sql = "DELETE FROM $incoming_tn WHERE id=$message_id";
2087         my $res = $incoming_db->exec_statement($sql);
2088     }
2090     $kernel->delay_set('watch_for_next_tasks', 1); 
2094 sub get_ldap_handle {
2095         my ($session_id) = @_;
2096         my $heap;
2097         my $ldap_handle;
2099         if (not defined $session_id ) { $session_id = 0 };
2100         if ($session_id =~ /[^0-9]*/) { $session_id = 0 };
2102         if ($session_id == 0) {
2103                 daemon_log("$session_id DEBUG: get_ldap_handle invoked without a session_id, create a new ldap_handle", 7); 
2104                 $ldap_handle = Net::LDAP->new( $ldap_uri );
2105                 if (defined $ldap_handle) {
2106                         $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!"); 
2107                 } else {
2108                         daemon_log("$session_id ERROR: creation of a new LDAP handle failed (ldap_uri '$ldap_uri')");
2109                 }
2111         } else {
2112                 my $session_reference = $global_kernel->ID_id_to_session($session_id);
2113                 if( defined $session_reference ) {
2114                         $heap = $session_reference->get_heap();
2115                 }
2117                 if (not defined $heap) {
2118                         daemon_log("$session_id DEBUG: cannot get heap for session_id '$session_id'", 7); 
2119                         return;
2120                 }
2122                 # TODO: This "if" is nonsense, because it doesn't prove that the
2123                 #       used handle is still valid - or if we've to reconnect...
2124                 #if (not exists $heap->{ldap_handle}) {
2125                         $ldap_handle = Net::LDAP->new( $ldap_uri );
2126                         $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!"); 
2127                         $heap->{ldap_handle} = $ldap_handle;
2128                 #}
2129         }
2130         return $ldap_handle;
2134 sub change_fai_state {
2135     my ($st, $targets, $session_id) = @_;
2136     $session_id = 0 if not defined $session_id;
2137     # Set FAI state to localboot
2138     my %mapActions= (
2139         reboot    => '',
2140         update    => 'softupdate',
2141         localboot => 'localboot',
2142         reinstall => 'install',
2143         rescan    => '',
2144         wake      => '',
2145         memcheck  => 'memcheck',
2146         sysinfo   => 'sysinfo',
2147         install   => 'install',
2148     );
2150     # Return if this is unknown
2151     if (!exists $mapActions{ $st }){
2152         daemon_log("$session_id ERROR: unknown action '$st', can not translate ot FAIstate", 1); 
2153       return;
2154     }
2156     my $state= $mapActions{ $st };
2158     my $ldap_handle = &get_ldap_handle($session_id);
2159     if( defined($ldap_handle) ) {
2161       # Build search filter for hosts
2162         my $search= "(&(objectClass=GOhard)";
2163         foreach (@{$targets}){
2164             $search.= "(macAddress=$_)";
2165         }
2166         $search.= ")";
2168       # If there's any host inside of the search string, procress them
2169         if (!($search =~ /macAddress/)){
2170             daemon_log("$session_id ERROR: no macAddress found in filter statement for LDAP search: '$search'", 1);    
2171             return;
2172         }
2174       # Perform search for Unit Tag
2175       my $mesg = $ldap_handle->search(
2176           base   => $ldap_base,
2177           scope  => 'sub',
2178           attrs  => ['dn', 'FAIstate', 'objectClass'],
2179           filter => "$search"
2180           );
2182           if ($mesg->count) {
2183                   my @entries = $mesg->entries;
2184                   if (0 == @entries) {
2185                                   daemon_log("$session_id ERROR: ldap search failed: ldap_base=$ldap_base, filter=$search", 1); 
2186                   }
2188                   foreach my $entry (@entries) {
2189                           # Only modify entry if it is not set to '$state'
2190                           if ($entry->get_value("FAIstate") ne "$state"){
2191                                   daemon_log("$session_id INFO: Setting FAIstate to '$state' for ".$entry->dn, 5);
2192                                   my $result;
2193                                   my %tmp = map { $_ => 1 } $entry->get_value("objectClass");
2194                                   if (exists $tmp{'FAIobject'}){
2195                                           if ($state eq ''){
2196                                                   $result= $ldap_handle->modify($entry->dn, changes => [ delete => [ FAIstate => [] ] ]);
2197                                           } else {
2198                                                   $result= $ldap_handle->modify($entry->dn, changes => [ replace => [ FAIstate => $state ] ]);
2199                                           }
2200                                   } elsif ($state ne ''){
2201                                           $result= $ldap_handle->modify($entry->dn, changes => [ add => [ objectClass => 'FAIobject' ], add => [ FAIstate => $state ] ]);
2202                                   }
2204                                   # Errors?
2205                                   if ($result->code){
2206                                           daemon_log("$session_id Error: Setting FAIstate to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
2207                                   }
2208                           } else {
2209                                   daemon_log("$session_id DEBUG FAIstate at host '".$entry->dn."' already at state '$st'", 7); 
2210                           }  
2211                   }
2212           } else {
2213                 daemon_log("$session_id ERROR: LDAP search failed: ldap_base=$ldap_base, filter=$search", 1);
2214           }
2216     # if no ldap handle defined
2217     } else {
2218         daemon_log("$session_id ERROR: no LDAP handle defined for update FAIstate", 1); 
2219     }
2221         return;
2225 sub change_goto_state {
2226     my ($st, $targets, $session_id) = @_;
2227     $session_id = 0  if not defined $session_id;
2229     # Switch on or off?
2230     my $state= $st eq 'active' ? 'active': 'locked';
2232     my $ldap_handle = &get_ldap_handle($session_id);
2233     if( defined($ldap_handle) ) {
2235       # Build search filter for hosts
2236       my $search= "(&(objectClass=GOhard)";
2237       foreach (@{$targets}){
2238         $search.= "(macAddress=$_)";
2239       }
2240       $search.= ")";
2242       # If there's any host inside of the search string, procress them
2243       if (!($search =~ /macAddress/)){
2244         return;
2245       }
2247       # Perform search for Unit Tag
2248       my $mesg = $ldap_handle->search(
2249           base   => $ldap_base,
2250           scope  => 'sub',
2251           attrs  => ['dn', 'gotoMode'],
2252           filter => "$search"
2253           );
2255       if ($mesg->count) {
2256         my @entries = $mesg->entries;
2257         foreach my $entry (@entries) {
2259           # Only modify entry if it is not set to '$state'
2260           if ($entry->get_value("gotoMode") ne $state){
2262             daemon_log("$session_id INFO: Setting gotoMode to '$state' for ".$entry->dn, 5);
2263             my $result;
2264             $result= $ldap_handle->modify($entry->dn, changes => [replace => [ gotoMode => $state ] ]);
2266             # Errors?
2267             if ($result->code){
2268               &daemon_log("$session_id Error: Setting gotoMode to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
2269             }
2271           }
2272         }
2273       } else {
2274                 daemon_log("$session_id ERROR: LDAP search failed in function change_goto_state: ldap_base=$ldap_base, filter=$search", 1);
2275           }
2277     }
2281 sub run_recreate_packages_db {
2282     my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
2283     my $session_id = $session->ID;
2284         &main::daemon_log("$session_id INFO: Recreating FAI Packages DB ('$fai_release_tn', '$fai_server_tn', '$packages_list_tn')", 5);
2285         $kernel->yield('create_fai_release_db', $fai_release_tn);
2286         $kernel->yield('create_fai_server_db', $fai_server_tn);
2287         return;
2291 sub run_create_fai_server_db {
2292     my ($kernel, $session, $heap, $table_name) = @_[KERNEL, SESSION, HEAP, ARG0];
2293     my $session_id = $session->ID;
2294     my $task = POE::Wheel::Run->new(
2295             Program => sub { &create_fai_server_db($table_name,$kernel, undef, $session_id) },
2296             StdoutEvent  => "session_run_result",
2297             StderrEvent  => "session_run_debug",
2298             CloseEvent   => "session_run_done",
2299             );
2301     $heap->{task}->{ $task->ID } = $task;
2302     return;
2306 sub create_fai_server_db {
2307         my ($table_name, $kernel, $dont_create_packages_list, $session_id) = @_;
2308         my $result;
2310         if (not defined $session_id) { $session_id = 0; }
2311         my $ldap_handle = &get_ldap_handle();
2312         if(defined($ldap_handle)) {
2313                 daemon_log("$session_id INFO: create_fai_server_db: start", 5);
2314                 my $mesg= $ldap_handle->search(
2315                         base   => $ldap_base,
2316                         scope  => 'sub',
2317                         attrs  => ['FAIrepository', 'gosaUnitTag'],
2318                         filter => "(&(FAIrepository=*)(objectClass=FAIrepositoryServer))",
2319                 );
2320                 if($mesg->{'resultCode'} == 0 &&
2321                         $mesg->count != 0) {
2322                         foreach my $entry (@{$mesg->{entries}}) {
2323                                 if($entry->exists('FAIrepository')) {
2324                                         # Add an entry for each Repository configured for server
2325                                         foreach my $repo(@{$entry->get_value('FAIrepository', asref => 1)}) {
2326                                                 my($tmp_url,$tmp_server,$tmp_release,$tmp_sections) = split(/\|/, $repo);
2327                                                 my $tmp_tag= $entry->get_value('gosaUnitTag') || "";
2328                                                 $result= $fai_server_db->add_dbentry( { 
2329                                                                 table => $table_name,
2330                                                                 primkey => ['server', 'fai_release', 'tag'],
2331                                                                 server => $tmp_url,
2332                                                                 fai_release => $tmp_release,
2333                                                                 sections => $tmp_sections,
2334                                                                 tag => (length($tmp_tag)>0)?$tmp_tag:"",
2335                                                         } );
2336                                         }
2337                                 }
2338                         }
2339                 }
2340                 daemon_log("$session_id INFO: create_fai_server_db: finished", 5);
2342                 # TODO: Find a way to post the 'create_packages_list_db' event
2343                 if(not defined($dont_create_packages_list)) {
2344                         &create_packages_list_db(undef, undef, $session_id);
2345                 }
2346         }       
2348         $ldap_handle->disconnect;
2349         return $result;
2353 sub run_create_fai_release_db {
2354         my ($session, $heap, $table_name) = @_[SESSION, HEAP, ARG0];
2355         my $session_id = $session->ID;
2356         my $task = POE::Wheel::Run->new(
2357                 Program => sub { &create_fai_release_db($table_name, $session_id) },
2358                 StdoutEvent  => "session_run_result",
2359                 StderrEvent  => "session_run_debug",
2360                 CloseEvent   => "session_run_done",
2361         );
2363         $heap->{task}->{ $task->ID } = $task;
2364         return;
2368 sub create_fai_release_db {
2369         my ($table_name, $session_id) = @_;
2370         my $result;
2372         # used for logging
2373         if (not defined $session_id) { $session_id = 0; }
2375         my $ldap_handle = &get_ldap_handle();
2376         if(defined($ldap_handle)) {
2377                 daemon_log("$session_id INFO: create_fai_release_db: start",5);
2378                 my $mesg= $ldap_handle->search(
2379                         base   => $ldap_base,
2380                         scope  => 'sub',
2381                         attrs  => [],
2382                         filter => "(&(objectClass=organizationalUnit)(ou=fai))",
2383                 );
2384                 if($mesg->{'resultCode'} == 0 &&
2385                         $mesg->count != 0) {
2386                         # Walk through all possible FAI container ou's
2387                         my @sql_list;
2388                         my $timestamp= &get_time();
2389                         foreach my $ou (@{$mesg->{entries}}) {
2390                                 my $tmp_classes= resolve_fai_classes($ou->dn, $ldap_handle, $session_id);
2391                                 if(defined($tmp_classes) && ref($tmp_classes) eq 'HASH') {
2392                                         my @tmp_array=get_fai_release_entries($tmp_classes);
2393                                         if(@tmp_array) {
2394                                                 foreach my $entry (@tmp_array) {
2395                                                         if(defined($entry) && ref($entry) eq 'HASH') {
2396                                                                 my $sql= 
2397                                                                 "INSERT INTO $table_name "
2398                                                                 ."(timestamp, fai_release, class, type, state) VALUES ("
2399                                                                 .$timestamp.","
2400                                                                 ."'".$entry->{'release'}."',"
2401                                                                 ."'".$entry->{'class'}."',"
2402                                                                 ."'".$entry->{'type'}."',"
2403                                                                 ."'".$entry->{'state'}."')";
2404                                                                 push @sql_list, $sql;
2405                                                         }
2406                                                 }
2407                                         }
2408                                 }
2409                         }
2411                         daemon_log("$session_id DEBUG: Inserting ".scalar @sql_list." entries to DB",8);
2412                         if(@sql_list) {
2413                                 unshift @sql_list, "DELETE FROM $table_name";
2414                                 $fai_release_db->exec_statementlist(\@sql_list);
2415                         }
2416                         daemon_log("$session_id DEBUG: Done with inserting",7);
2417                 }
2418                 daemon_log("$session_id INFO: create_fai_release_db: finished",5);
2419         }
2420         $ldap_handle->disconnect;
2421         return $result;
2424 sub get_fai_types {
2425         my $tmp_classes = shift || return undef;
2426         my @result;
2428         foreach my $type(keys %{$tmp_classes}) {
2429                 if(defined($tmp_classes->{$type}[0]) && (!($tmp_classes->{$type}[0] =~ /^.*?removed.*?$/))) {
2430                         my $entry = {
2431                                 type => $type,
2432                                 state => $tmp_classes->{$type}[0],
2433                         };
2434                         push @result, $entry;
2435                 }
2436         }
2438         return @result;
2441 sub get_fai_state {
2442         my $result = "";
2443         my $tmp_classes = shift || return $result;
2445         foreach my $type(keys %{$tmp_classes}) {
2446                 if(defined($tmp_classes->{$type}[0])) {
2447                         $result = $tmp_classes->{$type}[0];
2448                         
2449                 # State is equal for all types in class
2450                         last;
2451                 }
2452         }
2454         return $result;
2457 sub resolve_fai_classes {
2458         my ($fai_base, $ldap_handle, $session_id) = @_;
2459         if (not defined $session_id) { $session_id = 0; }
2460         my $result;
2461         my @possible_fai_classes= ("FAIscript", "FAIhook", "FAIpartitionTable", "FAItemplate", "FAIvariable", "FAIprofile", "FAIpackageList");
2462         my $fai_filter= "(|(&(objectClass=FAIclass)(|(objectClass=".join(")(objectClass=", @possible_fai_classes).")))(objectClass=FAIbranch))";
2463         my $fai_classes;
2465         daemon_log("$session_id DEBUG: Searching for FAI entries in base $fai_base",7);
2466         my $mesg= $ldap_handle->search(
2467                 base   => $fai_base,
2468                 scope  => 'sub',
2469                 attrs  => ['cn','objectClass','FAIstate'],
2470                 filter => $fai_filter,
2471         );
2472         daemon_log("$session_id DEBUG: Found ".$mesg->count()." FAI entries",7);
2474         if($mesg->{'resultCode'} == 0 &&
2475                 $mesg->count != 0) {
2476                 foreach my $entry (@{$mesg->{entries}}) {
2477                         if($entry->exists('cn')) {
2478                                 my $tmp_dn= $entry->dn();
2480                                 # Skip classname and ou dn parts for class
2481                                 my $tmp_release = ($1) if $tmp_dn =~ /^[^,]+,[^,]+,(.*?),$fai_base$/;
2483                                 # Skip classes without releases
2484                                 if((!defined($tmp_release)) || length($tmp_release)==0) {
2485                                         next;
2486                                 }
2488                                 my $tmp_cn= $entry->get_value('cn');
2489                                 my $tmp_state= $entry->get_value('FAIstate');
2491                                 my $tmp_type;
2492                                 # Get FAI type
2493                                 for my $oclass(@{$entry->get_value('objectClass', asref => 1)}) {
2494                                         if(grep $_ eq $oclass, @possible_fai_classes) {
2495                                                 $tmp_type= $oclass;
2496                                                 last;
2497                                         }
2498                                 }
2500                                 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
2501                                         # A Subrelease
2502                                         my @sub_releases = split(/,/, $tmp_release);
2504                                         # Walk through subreleases and build hash tree
2505                                         my $hash;
2506                                         while(my $tmp_sub_release = pop @sub_releases) {
2507                                                 $hash .= "\{'$tmp_sub_release'\}->";                                            
2508                                         }
2509                                         eval('push @{$fai_classes->'.$hash.'{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";');
2510                                 } else {
2511                                         # A branch, no subrelease
2512                                         push @{$fai_classes->{$tmp_release}->{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";
2513                                 }
2514                         } elsif (!$entry->exists('cn')) {
2515                                 my $tmp_dn= $entry->dn();
2516                                 my $tmp_release = ($1) if $tmp_dn =~ /^(.*?),$fai_base$/;
2518                                 # Skip classes without releases
2519                                 if((!defined($tmp_release)) || length($tmp_release)==0) {
2520                                         next;
2521                                 }
2523                                 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
2524                                         # A Subrelease
2525                                         my @sub_releases= split(/,/, $tmp_release);
2527                                         # Walk through subreleases and build hash tree
2528                                         my $hash;
2529                                         while(my $tmp_sub_release = pop @sub_releases) {
2530                                                 $hash .= "\{'$tmp_sub_release'\}->";                                            
2531                                         }
2532                                         # Remove the last two characters
2533                                         chop($hash);
2534                                         chop($hash);
2536                                         eval('$fai_classes->'.$hash.'= {}');
2537                                 } else {
2538                                         # A branch, no subrelease
2539                                         if(!exists($fai_classes->{$tmp_release})) {
2540                                                 $fai_classes->{$tmp_release} = {};
2541                                         }
2542                                 }
2543                         }
2544                 }
2546                 # The hash is complete, now we can honor the copy-on-write based missing entries
2547                 foreach my $release (keys %$fai_classes) {
2548                         $result->{$release}= deep_copy(apply_fai_inheritance($fai_classes->{$release}));
2549                 }
2550         }
2551         return $result;
2554 sub apply_fai_inheritance {
2555        my $fai_classes = shift || return {};
2556        my $tmp_classes;
2558        # Get the classes from the branch
2559        foreach my $class (keys %{$fai_classes}) {
2560                # Skip subreleases
2561                if($class =~ /^ou=.*$/) {
2562                        next;
2563                } else {
2564                        $tmp_classes->{$class}= deep_copy($fai_classes->{$class});
2565                }
2566        }
2568        # Apply to each subrelease
2569        foreach my $subrelease (keys %{$fai_classes}) {
2570                if($subrelease =~ /ou=/) {
2571                        foreach my $tmp_class (keys %{$tmp_classes}) {
2572                                if(!exists($fai_classes->{$subrelease}->{$tmp_class})) {
2573                                        $fai_classes->{$subrelease}->{$tmp_class} =
2574                                        deep_copy($tmp_classes->{$tmp_class});
2575                                } else {
2576                                        foreach my $type (keys %{$tmp_classes->{$tmp_class}}) {
2577                                                if(!exists($fai_classes->{$subrelease}->{$tmp_class}->{$type})) {
2578                                                        $fai_classes->{$subrelease}->{$tmp_class}->{$type}=
2579                                                        deep_copy($tmp_classes->{$tmp_class}->{$type});
2580                                                }
2581                                        }
2582                                }
2583                        }
2584                }
2585        }
2587        # Find subreleases in deeper levels
2588        foreach my $subrelease (keys %{$fai_classes}) {
2589                if($subrelease =~ /ou=/) {
2590                        foreach my $subsubrelease (keys %{$fai_classes->{$subrelease}}) {
2591                                if($subsubrelease =~ /ou=/) {
2592                                        apply_fai_inheritance($fai_classes->{$subrelease});
2593                                }
2594                        }
2595                }
2596        }
2598        return $fai_classes;
2601 sub get_fai_release_entries {
2602         my $tmp_classes = shift || return;
2603         my $parent = shift || "";
2604         my @result = shift || ();
2606         foreach my $entry (keys %{$tmp_classes}) {
2607                 if(defined($entry)) {
2608                         if($entry =~ /^ou=.*$/) {
2609                                 my $release_name = $entry;
2610                                 $release_name =~ s/ou=//g;
2611                                 if(length($parent)>0) {
2612                                         $release_name = $parent."/".$release_name;
2613                                 }
2614                                 my @bufentries = get_fai_release_entries($tmp_classes->{$entry}, $release_name, @result);
2615                                 foreach my $bufentry(@bufentries) {
2616                                         push @result, $bufentry;
2617                                 }
2618                         } else {
2619                                 my @types = get_fai_types($tmp_classes->{$entry});
2620                                 foreach my $type (@types) {
2621                                         push @result, 
2622                                         {
2623                                                 'class' => $entry,
2624                                                 'type' => $type->{'type'},
2625                                                 'release' => $parent,
2626                                                 'state' => $type->{'state'},
2627                                         };
2628                                 }
2629                         }
2630                 }
2631         }
2633         return @result;
2636 sub deep_copy {
2637         my $this = shift;
2638         if (not ref $this) {
2639                 $this;
2640         } elsif (ref $this eq "ARRAY") {
2641                 [map deep_copy($_), @$this];
2642         } elsif (ref $this eq "HASH") {
2643                 +{map { $_ => deep_copy($this->{$_}) } keys %$this};
2644         } else { die "what type is $_?" }
2648 sub session_run_result {
2649     my ($kernel, $heap, $client_answer) = @_[KERNEL, HEAP, ARG0];    
2650     $kernel->sig(CHLD => "child_reap");
2653 sub session_run_debug {
2654     my $result = $_[ARG0];
2655     print STDERR "$result\n";
2658 sub session_run_done {
2659     my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
2660     delete $heap->{task}->{$task_id};
2664 sub create_sources_list {
2665         my $session_id = shift;
2666         my $ldap_handle = &main::get_ldap_handle;
2667         my $result="/tmp/gosa_si_tmp_sources_list";
2669         # Remove old file
2670         if(stat($result)) {
2671                 unlink($result);
2672                 &main::daemon_log("$session_id DEBUG: remove an old version of '$result'", 7); 
2673         }
2675         my $fh;
2676         open($fh, ">$result");
2677         if (not defined $fh) {
2678                 &main::daemon_log("$session_id DEBUG: cannot open '$result' for writing", 7); 
2679                 return undef;
2680         }
2681         if(defined($main::ldap_server_dn) and length($main::ldap_server_dn) > 0) {
2682                 my $mesg=$ldap_handle->search(
2683                         base    => $main::ldap_server_dn,
2684                         scope   => 'base',
2685                         attrs   => 'FAIrepository',
2686                         filter  => 'objectClass=FAIrepositoryServer'
2687                 );
2688                 if($mesg->count) {
2689                         foreach my $entry(@{$mesg->{'entries'}}) {
2690                                 foreach my $value(@{$entry->get_value('FAIrepository', asref => 1)}) {
2691                                         my ($server, $tag, $release, $sections)= split /\|/, $value;
2692                                         my $line = "deb $server $release";
2693                                         $sections =~ s/,/ /g;
2694                                         $line.= " $sections";
2695                                         print $fh $line."\n";
2696                                 }
2697                         }
2698                 }
2699         } else {
2700                 if (defined $main::ldap_server_dn){
2701                         &main::daemon_log("$session_id ERROR: something wrong with ldap_server_dn '$main::ldap_server_dn', abort create_sources_list", 1); 
2702                 } else {
2703                         &main::daemon_log("$session_id ERROR: no ldap_server_dn found, abort create_sources_list", 1);
2704                 }
2705         }
2706         close($fh);
2708         return $result;
2712 sub run_create_packages_list_db {
2713     my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
2714         my $session_id = $session->ID;
2716         my $task = POE::Wheel::Run->new(
2717                                         Priority => +20,
2718                                         Program => sub {&create_packages_list_db(undef, undef, $session_id)},
2719                                         StdoutEvent  => "session_run_result",
2720                                         StderrEvent  => "session_run_debug",
2721                                         CloseEvent   => "session_run_done",
2722                                         );
2723         $heap->{task}->{ $task->ID } = $task;
2727 sub create_packages_list_db {
2728         my ($ldap_handle, $sources_file, $session_id) = @_;
2729         
2730         # it should not be possible to trigger a recreation of packages_list_db
2731         # while packages_list_db is under construction, so set flag packages_list_under_construction
2732         # which is tested befor recreation can be started
2733         if (-r $packages_list_under_construction) {
2734                 daemon_log("$session_id WARNING: packages_list_db is right now under construction, please wait until this process is finished", 3);
2735                 return;
2736         } else {
2737                 daemon_log("$session_id INFO: create_packages_list_db: start", 5); 
2738                 # set packages_list_under_construction to true
2739                 system("touch $packages_list_under_construction");
2740                 @packages_list_statements=();
2741         }
2743         if (not defined $session_id) { $session_id = 0; }
2744         if (not defined $ldap_handle) { 
2745                 $ldap_handle= &get_ldap_handle();
2747                 if (not defined $ldap_handle) {
2748                         daemon_log("$session_id ERROR: no ldap_handle available to create_packages_list_db", 1);
2749                         unlink($packages_list_under_construction);
2750                         return;
2751                 }
2752         }
2753         if (not defined $sources_file) { 
2754                 &main::daemon_log("$session_id INFO: no sources_file given for creating packages list so trigger creation of it", 5); 
2755                 $sources_file = &create_sources_list($session_id);
2756         }
2758         if (not defined $sources_file) {
2759                 &main::daemon_log("$session_id ERROR: no sources_file given under '$sources_file', skip create_packages_list_db", 1); 
2760                 unlink($packages_list_under_construction);
2761                 return;
2762         }
2764         my $line;
2766         open(CONFIG, "<$sources_file") or do {
2767                 daemon_log( "$session_id ERROR: create_packages_list_db: Failed to open '$sources_file'", 1);
2768                 unlink($packages_list_under_construction);
2769                 return;
2770         };
2772         # Read lines
2773         while ($line = <CONFIG>){
2774                 # Unify
2775                 chop($line);
2776                 $line =~ s/^\s+//;
2777                 $line =~ s/^\s+/ /;
2779                 # Strip comments
2780                 $line =~ s/#.*$//g;
2782                 # Skip empty lines
2783                 if ($line =~ /^\s*$/){
2784                         next;
2785                 }
2787                 # Interpret deb line
2788                 if ($line =~ /^deb [^\s]+\s[^\s]+\s[^\s]+/){
2789                         my( $baseurl, $dist, $sections ) = ($line =~ /^deb\s([^\s]+)\s+([^\s]+)\s+(.*)$/);
2790                         my $section;
2791                         foreach $section (split(' ', $sections)){
2792                                 &parse_package_info( $baseurl, $dist, $section, $session_id );
2793                         }
2794                 }
2795         }
2797         close (CONFIG);
2800         if(keys(%repo_dirs)) {
2801                 find(\&cleanup_and_extract, keys( %repo_dirs ));
2802                 &main::strip_packages_list_statements();
2803                 $packages_list_db->exec_statementlist(\@packages_list_statements);
2804         }
2805         unlink($packages_list_under_construction);
2806         daemon_log("$session_id INFO: create_packages_list_db: finished", 5); 
2807         return;
2810 # This function should do some intensive task to minimize the db-traffic
2811 sub strip_packages_list_statements {
2812     my @existing_entries= @{$packages_list_db->exec_statement("SELECT * FROM $main::packages_list_tn")};
2813         my @new_statement_list=();
2814         my $hash;
2815         my $insert_hash;
2816         my $update_hash;
2817         my $delete_hash;
2818         my $local_timestamp=get_time();
2820         foreach my $existing_entry (@existing_entries) {
2821                 $hash->{@{$existing_entry}[0]}->{@{$existing_entry}[1]}->{@{$existing_entry}[2]}= $existing_entry;
2822         }
2824         foreach my $statement (@packages_list_statements) {
2825                 if($statement =~ /^INSERT/i) {
2826                         # Assign the values from the insert statement
2827                         my ($distribution,$package,$version,$section,$description,$template,$timestamp) = ($1,$2,$3,$4,$5,$6,$7) if $statement =~ 
2828                         /^INSERT\s+?INTO\s+?$main::packages_list_tn\s+?VALUES\s*?\('(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)'\s*?\)$/si;
2829                         if(exists($hash->{$distribution}->{$package}->{$version})) {
2830                                 # If section or description has changed, update the DB
2831                                 if( 
2832                                         (! (@{$hash->{$distribution}->{$package}->{$version}}[3] eq $section)) or 
2833                                         (! (@{$hash->{$distribution}->{$package}->{$version}}[4] eq $description))
2834                                 ) {
2835                                         @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,undef);
2836                                 }
2837                         } else {
2838                                 # Insert a non-existing entry to db
2839                                 @{$insert_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
2840                         }
2841                 } elsif ($statement =~ /^UPDATE/i) {
2842                         my ($template,$package,$version) = ($1,$2,$3) if $statement =~
2843                         /^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;
2844                         foreach my $distribution (keys %{$hash}) {
2845                                 if(exists($insert_hash->{$distribution}->{$package}->{$version})) {
2846                                         # update the insertion hash to execute only one query per package (insert instead insert+update)
2847                                         @{$insert_hash->{$distribution}->{$package}->{$version}}[5]= $template;
2848                                 } elsif(exists($hash->{$distribution}->{$package}->{$version})) {
2849                                         if( ! (@{$hash->{$distribution}->{$package}->{$version}}[5] eq $template)) {
2850                                                 my $section;
2851                                                 my $description;
2852                                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) and
2853                                                         length(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) > 0 ) {
2854                                                         $section= @{$update_hash->{$distribution}->{$package}->{$version}}[3];
2855                                                 }
2856                                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
2857                                                         $description= @{$update_hash->{$distribution}->{$package}->{$version}}[4];
2858                                                 }
2859                                                 @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
2860                                         }
2861                                 }
2862                         }
2863                 }
2864         }
2866         # TODO: Check for orphaned entries
2868         # unroll the insert_hash
2869         foreach my $distribution (keys %{$insert_hash}) {
2870                 foreach my $package (keys %{$insert_hash->{$distribution}}) {
2871                         foreach my $version (keys %{$insert_hash->{$distribution}->{$package}}) {
2872                                 push @new_statement_list, "INSERT INTO $main::packages_list_tn VALUES ('$distribution','$package','$version',"
2873                                 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[3]',"
2874                                 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[4]',"
2875                                 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[5]',"
2876                                 ."'$local_timestamp')";
2877                         }
2878                 }
2879         }
2881         # unroll the update hash
2882         foreach my $distribution (keys %{$update_hash}) {
2883                 foreach my $package (keys %{$update_hash->{$distribution}}) {
2884                         foreach my $version (keys %{$update_hash->{$distribution}->{$package}}) {
2885                                 my $set = "";
2886                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3])) {
2887                                         $set .= "section = '@{$update_hash->{$distribution}->{$package}->{$version}}[3]', ";
2888                                 }
2889                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
2890                                         $set .= "description = '@{$update_hash->{$distribution}->{$package}->{$version}}[4]', ";
2891                                 }
2892                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[5])) {
2893                                         $set .= "template = '@{$update_hash->{$distribution}->{$package}->{$version}}[5]', ";
2894                                 }
2895                                 if(defined($set) and length($set) > 0) {
2896                                         $set .= "timestamp = '$local_timestamp'";
2897                                 } else {
2898                                         next;
2899                                 }
2900                                 push @new_statement_list, 
2901                                         "UPDATE $main::packages_list_tn SET $set WHERE"
2902                                         ." distribution = '$distribution'"
2903                                         ." AND package = '$package'"
2904                                         ." AND version = '$version'";
2905                         }
2906                 }
2907         }
2909         @packages_list_statements = @new_statement_list;
2913 sub parse_package_info {
2914     my ($baseurl, $dist, $section, $session_id)= @_;
2915     my ($package);
2916     if (not defined $session_id) { $session_id = 0; }
2917     my ($path) = ($baseurl =~ m%://[^/]*(.*)$%);
2918     $repo_dirs{ "${repo_path}/pool" } = 1;
2920     foreach $package ("Packages.gz"){
2921         daemon_log("$session_id DEBUG: create_packages_list: fetch $baseurl, $dist, $section", 7);
2922         get_package( "$baseurl/dists/$dist/$section/binary-$arch/$package", "$outdir/$dist/$section", $session_id );
2923         parse_package( "$outdir/$dist/$section", $dist, $path, $session_id );
2924     }
2925     
2929 sub get_package {
2930     my ($url, $dest, $session_id)= @_;
2931     if (not defined $session_id) { $session_id = 0; }
2933     my $tpath = dirname($dest);
2934     -d "$tpath" || mkpath "$tpath";
2936     # This is ugly, but I've no time to take a look at "how it works in perl"
2937     if(0 == system("wget '$url' -O '$dest' 2>/dev/null") ) {
2938         system("gunzip -cd '$dest' > '$dest.in'");
2939         daemon_log("$session_id DEBUG: run command: gunzip -cd '$dest' > '$dest.in'", 5);
2940         unlink($dest);
2941         daemon_log("$session_id DEBUG: delete file '$dest'", 5); 
2942     } else {
2943         daemon_log("$session_id ERROR: create_packages_list_db: get_packages: fetching '$url' failed!", 1);
2944     }
2945     return 0;
2949 sub parse_package {
2950     my ($path, $dist, $srv_path, $session_id)= @_;
2951     if (not defined $session_id) { $session_id = 0;}
2952     my ($package, $version, $section, $description);
2953     my $PACKAGES;
2954     my $timestamp = &get_time();
2956     if(not stat("$path.in")) {
2957         daemon_log("$session_id ERROR: create_packages_list: parse_package: file '$path.in' is not readable",1);
2958         return;
2959     }
2961     open($PACKAGES, "<$path.in");
2962     if(not defined($PACKAGES)) {
2963         daemon_log("$session_id ERROR: create_packages_list_db: parse_package: cannot open '$path.in'",1); 
2964         return;
2965     }
2967     # Read lines
2968     while (<$PACKAGES>){
2969         my $line = $_;
2970         # Unify
2971         chop($line);
2973         # Use empty lines as a trigger
2974         if ($line =~ /^\s*$/){
2975             my $sql = "INSERT INTO packages_list VALUES ('$dist', '$package', '$version', '$section', '$description', '', '$timestamp')";
2976             push(@packages_list_statements, $sql);
2977             $package = "none";
2978             $version = "none";
2979             $section = "none";
2980             $description = "none"; 
2981             next;
2982         }
2984         # Trigger for package name
2985         if ($line =~ /^Package:\s/){
2986             ($package)= ($line =~ /^Package: (.*)$/);
2987             next;
2988         }
2990         # Trigger for version
2991         if ($line =~ /^Version:\s/){
2992             ($version)= ($line =~ /^Version: (.*)$/);
2993             next;
2994         }
2996         # Trigger for description
2997         if ($line =~ /^Description:\s/){
2998             ($description)= &encode_base64(($line =~ /^Description: (.*)$/));
2999             next;
3000         }
3002         # Trigger for section
3003         if ($line =~ /^Section:\s/){
3004             ($section)= ($line =~ /^Section: (.*)$/);
3005             next;
3006         }
3008         # Trigger for filename
3009         if ($line =~ /^Filename:\s/){
3010             my ($filename) = ($line =~ /^Filename: (.*)$/);
3011             store_fileinfo( $package, $filename, $dist, $srv_path, $version, $repo_path );
3012             next;
3013         }
3014     }
3016     close( $PACKAGES );
3017     unlink( "$path.in" );
3021 sub store_fileinfo {
3022     my( $package, $file, $dist, $path, $vers, $srvdir) = @_;
3024     my %fileinfo = (
3025         'package' => $package,
3026         'dist' => $dist,
3027         'version' => $vers,
3028     );
3030     $repo_files{ "${srvdir}/$file" } = \%fileinfo;
3034 sub cleanup_and_extract {
3035         my $fileinfo = $repo_files{ $File::Find::name };
3037         if( defined $fileinfo ) {
3038                 my $dir = "$outdir/$fileinfo->{ 'dist' }/debconf.d";
3039                 my $sql;
3040                 my $package = $fileinfo->{ 'package' };
3041                 my $newver = $fileinfo->{ 'version' };
3043                 mkpath($dir);
3044                 system( "dpkg -e '$File::Find::name' '$dir/DEBIAN'" );
3046                 if( -f "$dir/DEBIAN/templates" ) {
3048                         daemon_log("DEBUG: Found debconf templates in '$package' - $newver", 7);
3050                         my $tmpl= ""; {
3051                                 local $/=undef;
3052                                 open FILE, "$dir/DEBIAN/templates";
3053                                 $tmpl = &encode_base64(<FILE>);
3054                                 close FILE;
3055                         }
3056                         rmtree("$dir/DEBIAN/templates");
3058                         $sql= "update $main::packages_list_tn set template = '$tmpl' where package = '$package' and version = '$newver';";
3059                         push @packages_list_statements, $sql;
3060                 }
3061         }
3063         return;
3067 sub register_at_foreign_servers {   
3068     my ($kernel) = $_[KERNEL];
3070     # hole alle bekannten server aus known_server_db
3071     my $server_sql = "SELECT * FROM $known_server_tn";
3072     my $server_res = $known_server_db->exec_statement($server_sql);
3074     # no entries in known_server_db
3075     if (not ref(@$server_res[0]) eq "ARRAY") { 
3076         # TODO
3077     }
3079     # detect already connected clients
3080     my $client_sql = "SELECT * FROM $known_clients_tn"; 
3081     my $client_res = $known_clients_db->exec_statement($client_sql);
3083     # send my server details to all other gosa-si-server within the network
3084     foreach my $hit (@$server_res) {
3085         my $hostname = @$hit[0];
3086         my $hostkey = &create_passwd;
3088         # add already connected clients to registration message 
3089         my $myhash = &create_xml_hash('new_server', $server_address, $hostname);
3090         &add_content2xml_hash($myhash, 'key', $hostkey);
3091         map(&add_content2xml_hash($myhash, 'client', @{$_}[0].",".@{$_}[4]), @$client_res);
3093         # add locally loaded gosa-si modules to registration message
3094         my $loaded_modules = {};
3095         while (my ($package, $pck_info) = each %$known_modules) {
3096                                                 next if ((!defined(@$pck_info[2])) || (!(ref (@$pck_info[2]) eq 'HASH')));
3097                                                 foreach my $act_module (keys(%{@$pck_info[2]})) {
3098                                                         $loaded_modules->{$act_module} = ""; 
3099                                                 }
3100         }
3102         map(&add_content2xml_hash($myhash, "loaded_modules", $_), keys(%$loaded_modules));
3104         # add macaddress to registration message
3105         my ($host_ip, $host_port) = split(/:/, $hostname);
3106         my $local_ip = &get_local_ip_for_remote_ip($host_ip);
3107         my $network_interface= &get_interface_for_ip($local_ip);
3108         my $host_mac = &get_mac_for_interface($network_interface);
3109         &add_content2xml_hash($myhash, 'macaddress', $host_mac);
3110         
3111         # build registration message and send it
3112         my $foreign_server_msg = &create_xml_string($myhash);
3113         my $error = &send_msg_to_target($foreign_server_msg, $hostname, $ServerPackages_key, "new_server", 0); 
3114     }
3115     
3116     $kernel->delay_set("register_at_foreign_servers", $foreign_servers_register_delay); 
3117     return;
3121 #==== MAIN = main ==============================================================
3122 #  parse commandline options
3123 Getopt::Long::Configure( "bundling" );
3124 GetOptions("h|help" => \&usage,
3125         "c|config=s" => \$cfg_file,
3126         "f|foreground" => \$foreground,
3127         "v|verbose+" => \$verbose,
3128         "no-arp+" => \$no_arp,
3129            );
3131 #  read and set config parameters
3132 &check_cmdline_param ;
3133 &read_configfile($cfg_file, %cfg_defaults);
3134 &check_pid;
3136 $SIG{CHLD} = 'IGNORE';
3138 # forward error messages to logfile
3139 if( ! $foreground ) {
3140   open( STDIN,  '+>/dev/null' );
3141   open( STDOUT, '+>&STDIN'    );
3142   open( STDERR, '+>&STDIN'    );
3145 # Just fork, if we are not in foreground mode
3146 if( ! $foreground ) { 
3147     chdir '/'                 or die "Can't chdir to /: $!";
3148     $pid = fork;
3149     setsid                    or die "Can't start a new session: $!";
3150     umask 0;
3151 } else { 
3152     $pid = $$; 
3155 # Do something useful - put our PID into the pid_file
3156 if( 0 != $pid ) {
3157     open( LOCK_FILE, ">$pid_file" );
3158     print LOCK_FILE "$pid\n";
3159     close( LOCK_FILE );
3160     if( !$foreground ) { 
3161         exit( 0 ) 
3162     };
3165 # parse head url and revision from svn
3166 my $server_status_hash = { 'developmental'=>'revision', 'stable'=>'release'};
3167 $server_version =~ /^\$HeadURL: (\S+) \$:\$Rev: (\d+) \$$/;
3168 $server_headURL = defined $1 ? $1 : 'unknown' ;
3169 $server_revision = defined $2 ? $2 : 'unknown' ;
3170 if ($server_headURL =~ /\/tag\// || 
3171         $server_headURL =~ /\/branches\// ) {
3172     $server_status = "stable"; 
3173 } else {
3174     $server_status = "developmental" ;
3177 # Prepare log file
3178 $root_uid = getpwnam('root');
3179 $adm_gid = getgrnam('adm');
3180 chmod(0640, $log_file);
3181 chown($root_uid, $adm_gid, $log_file);
3182 chown($root_uid, $adm_gid, "/var/lib/gosa-si");
3184 daemon_log(" ", 1);
3185 daemon_log("$0 started!", 1);
3186 daemon_log("status: $server_status", 1);
3187 daemon_log($server_status_hash->{$server_status}.": $server_revision", 1); 
3190     no strict "refs";
3192     if ($db_module eq "DBmysql") {
3193         # connect to incoming_db
3194         $incoming_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3196         # connect to gosa-si job queue
3197         $job_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3199         # connect to known_clients_db
3200         $known_clients_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3202         # connect to foreign_clients_db
3203         $foreign_clients_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3205         # connect to known_server_db
3206         $known_server_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3208         # connect to login_usr_db
3209         $login_users_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3211         # connect to fai_server_db 
3212         $fai_server_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3214         # connect to fai_release_db
3215         $fai_release_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3217         # connect to packages_list_db
3218         $packages_list_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3220         # connect to messaging_db
3221         $messaging_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3223     } elsif ($db_module eq "DBsqlite") {
3224         # connect to incoming_db
3225         unlink($incoming_file_name);
3226         $incoming_db = GOSA::DBsqlite->new($incoming_file_name);
3227         
3228         # connect to gosa-si job queue
3229         unlink($job_queue_file_name);  ## just for debugging
3230         $job_db = GOSA::DBsqlite->new($job_queue_file_name);
3231         chmod(0660, $job_queue_file_name);
3232         chown($root_uid, $adm_gid, $job_queue_file_name);
3233         
3234         # connect to known_clients_db
3235         unlink($known_clients_file_name);   ## just for debugging
3236         $known_clients_db = GOSA::DBsqlite->new($known_clients_file_name);
3237         chmod(0660, $known_clients_file_name);
3238         chown($root_uid, $adm_gid, $known_clients_file_name);
3239         
3240         # connect to foreign_clients_db
3241         unlink($foreign_clients_file_name);
3242         $foreign_clients_db = GOSA::DBsqlite->new($foreign_clients_file_name);
3243         chmod(0660, $foreign_clients_file_name);
3244         chown($root_uid, $adm_gid, $foreign_clients_file_name);
3245         
3246         # connect to known_server_db
3247         unlink($known_server_file_name);
3248         $known_server_db = GOSA::DBsqlite->new($known_server_file_name);
3249         chmod(0660, $known_server_file_name);
3250         chown($root_uid, $adm_gid, $known_server_file_name);
3251         
3252         # connect to login_usr_db
3253         unlink($login_users_file_name);
3254         $login_users_db = GOSA::DBsqlite->new($login_users_file_name);
3255         chmod(0660, $login_users_file_name);
3256         chown($root_uid, $adm_gid, $login_users_file_name);
3257         
3258         # connect to fai_server_db
3259         unlink($fai_server_file_name);
3260         $fai_server_db = GOSA::DBsqlite->new($fai_server_file_name);
3261         chmod(0660, $fai_server_file_name);
3262         chown($root_uid, $adm_gid, $fai_server_file_name);
3263         
3264         # connect to fai_release_db
3265         unlink($fai_release_file_name);
3266         $fai_release_db = GOSA::DBsqlite->new($fai_release_file_name);
3267         chmod(0660, $fai_release_file_name);
3268         chown($root_uid, $adm_gid, $fai_release_file_name);
3269         
3270         # connect to packages_list_db
3271         #unlink($packages_list_file_name);
3272         unlink($packages_list_under_construction);
3273         $packages_list_db = GOSA::DBsqlite->new($packages_list_file_name);
3274         chmod(0660, $packages_list_file_name);
3275         chown($root_uid, $adm_gid, $packages_list_file_name);
3276         
3277         # connect to messaging_db
3278         unlink($messaging_file_name);
3279         $messaging_db = GOSA::DBsqlite->new($messaging_file_name);
3280         chmod(0660, $messaging_file_name);
3281         chown($root_uid, $adm_gid, $messaging_file_name);
3282     }
3285 # Creating tables
3286 $messaging_db->create_table($messaging_tn, \@messaging_col_names);
3287 $packages_list_db->create_table($packages_list_tn, \@packages_list_col_names);
3288 $fai_release_db->create_table($fai_release_tn, \@fai_release_col_names);
3289 $fai_server_db->create_table($fai_server_tn, \@fai_server_col_names);
3290 $login_users_db->create_table($login_users_tn, \@login_users_col_names);
3291 $known_server_db->create_table($known_server_tn, \@known_server_col_names);
3292 $foreign_clients_db->create_table($foreign_clients_tn, \@foreign_clients_col_names);
3293 $known_clients_db->create_table($known_clients_tn, \@known_clients_col_names);
3294 $incoming_db->create_table($incoming_tn, \@incoming_col_names);
3295 $job_db->create_table($job_queue_tn, \@job_queue_col_names);
3298 # create xml object used for en/decrypting
3299 $xml = new XML::Simple();
3302 # foreign servers 
3303 my @foreign_server_list;
3305 # add foreign server from cfg file
3306 if ($foreign_server_string ne "") {
3307     my @cfg_foreign_server_list = split(",", $foreign_server_string);
3308     foreach my $foreign_server (@cfg_foreign_server_list) {
3309         push(@foreign_server_list, $foreign_server);
3310     }
3312     daemon_log("0 INFO: found foreign server in config file: ".join(", ", @foreign_server_list), 5);
3315 # Perform a DNS lookup for server registration if flag is true
3316 if ($dns_lookup eq "true") {
3317     # Add foreign server from dns
3318     my @tmp_servers;
3319     if (not $server_domain) {
3320         # Try our DNS Searchlist
3321         for my $domain(get_dns_domains()) {
3322             chomp($domain);
3323             my ($tmp_domains, $error_string) = &get_server_addresses($domain);
3324             if(@$tmp_domains) {
3325                 for my $tmp_server(@$tmp_domains) {
3326                     push @tmp_servers, $tmp_server;
3327                 }
3328             }
3329         }
3330         if(@tmp_servers && length(@tmp_servers)==0) {
3331             daemon_log("0 WARNING: no foreign gosa-si-server found in DNS for domain '$server_domain'", 3);
3332         }
3333     } else {
3334         @tmp_servers = &get_server_addresses($server_domain);
3335         if( 0 == @tmp_servers ) {
3336             daemon_log("0 WARNING: no foreign gosa-si-server found in DNS for domain '$server_domain'", 3);
3337         }
3338     }
3340     daemon_log("0 INFO: found foreign server via DNS ".join(", ", @tmp_servers), 5);    
3342     foreach my $server (@tmp_servers) { 
3343         unshift(@foreign_server_list, $server); 
3344     }
3345 } else {
3346     daemon_log("0 INFO: DNS lookup for server registration is disabled", 5);
3350 # eliminate duplicate entries
3351 @foreign_server_list = &del_doubles(@foreign_server_list);
3352 my $all_foreign_server = join(", ", @foreign_server_list);
3353 daemon_log("0 INFO: found foreign server in config file and DNS: '$all_foreign_server'", 5);
3355 # add all found foreign servers to known_server
3356 my $act_timestamp = &get_time();
3357 foreach my $foreign_server (@foreign_server_list) {
3359         # do not add myself to known_server_db
3360         if (&is_local($foreign_server)) { next; }
3361         ######################################
3363     my $res = $known_server_db->add_dbentry( {table=>$known_server_tn, 
3364             primkey=>['hostname'],
3365             hostname=>$foreign_server,
3366             macaddress=>"",
3367             status=>'not_jet_registered',
3368             hostkey=>"none",
3369             loaded_modules => "none", 
3370             timestamp=>$act_timestamp,
3371             } );
3375 # Import all modules
3376 &import_modules;
3378 # Check wether all modules are gosa-si valid passwd check
3379 &password_check;
3381 # Prepare for using Opsi 
3382 if ($opsi_enabled eq "true") {
3383     use JSON::RPC::Client;
3384     use XML::Quote qw(:all);
3385     $opsi_url= "https://".$opsi_admin.":".$opsi_password."@".$opsi_server.":4447/rpc";
3386     $opsi_client = new JSON::RPC::Client;
3390 POE::Component::Server::TCP->new(
3391         Alias => "TCP_SERVER",
3392         Port => $server_port,
3393         ClientInput => sub {
3394                 my ($kernel, $input, $heap, $session) = @_[KERNEL, ARG0, HEAP, SESSION];
3395         my $session_id = $session->ID;
3396         my $remote_ip = $heap->{'remote_ip'};
3397                 push(@msgs_to_decrypt, $input);
3398         &daemon_log("$session_id DEBUG: incoming message from '$remote_ip'", 7);
3399                 $kernel->yield("msg_to_decrypt");
3400         },
3401         InlineStates => {
3402                 msg_to_decrypt => \&msg_to_decrypt,
3403                 next_task => \&next_task,
3404                 task_result => \&handle_task_result,
3405                 task_done   => \&handle_task_done,
3406                 task_debug  => \&handle_task_debug,
3407                 child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!"  },
3408         }
3409 );
3411 daemon_log("0 INFO: start socket for incoming xml messages at port '$server_port' ", 1);
3413 # create session for repeatedly checking the job queue for jobs
3414 POE::Session->create(
3415         inline_states => {
3416                 _start => \&session_start,
3417         register_at_foreign_servers => \&register_at_foreign_servers,
3418         sig_handler => \&sig_handler,
3419         next_task => \&next_task,
3420         task_result => \&handle_task_result,
3421         task_done   => \&handle_task_done,
3422         task_debug  => \&handle_task_debug,
3423         watch_for_next_tasks => \&watch_for_next_tasks,
3424         watch_for_new_messages => \&watch_for_new_messages,
3425         watch_for_delivery_messages => \&watch_for_delivery_messages,
3426         watch_for_done_messages => \&watch_for_done_messages,
3427                 watch_for_new_jobs => \&watch_for_new_jobs,
3428         watch_for_modified_jobs => \&watch_for_modified_jobs,
3429         watch_for_done_jobs => \&watch_for_done_jobs,
3430         watch_for_opsi_jobs => \&watch_for_opsi_jobs,
3431         watch_for_old_known_clients => \&watch_for_old_known_clients,
3432         create_packages_list_db => \&run_create_packages_list_db,
3433         create_fai_server_db => \&run_create_fai_server_db,
3434         create_fai_release_db => \&run_create_fai_release_db,
3435                 recreate_packages_db => \&run_recreate_packages_db,
3436         session_run_result => \&session_run_result,
3437         session_run_debug => \&session_run_debug,
3438         session_run_done => \&session_run_done,
3439         child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!"  },
3440         }
3441 );
3444 POE::Kernel->run();
3445 exit;