Code

88e13681e45cdca30dd594b9091c1d8596065036
[gosa.git] / gosa-si / gosa-si-server
1 #!/usr/bin/perl
2 #*********************************************************************
3 #
4 # gosa-si-client -- client for the gosa-si-server
5 #
6 # (c) 2007-2009 by Andreas Rettenberger <rettenberger@gonicus.de>
7 # (c) 2008-2010 by Cajus Pollmeier <pollmeier@gonicus.de>
8 # (c) 2008-2009 by Jan Wenzel <wenzel@gonicus.de>
9 # (c) 2010 by Benoit Mortier <benoit.mortier@opensides.be>
10 #
11 #*********************************************************************
14 =head1 NAME
16 gosa-si-server -Support infrastructure for GOsa
18 =head1 SYNOPSIS
20 gosa-si-server [-hvf] [-c config] [-x dump ]
22 =head1 OPTIONS
24 B<-h>, B<--help>
25     print out this help message
27 B<-v>, B<--verbose>
28     be verbose (multiple v's will increase verbosity) 
29     -v          ERROR level
30     -vvv        WARNING  + ERROR level
31     -vvvvv      INFO + WARNING level
32     -vvvvvvv    DEBUG + INFO level
33     -vvvvvvvvv  in and out going xml messages will be displayed
35 B<-f>, B<--foreground> 
36     foregroud, process will not be forked to background
38 B<-c> I<file>, B<--config=>I<file>
39     configuration file, default F</etc/gosa-si/server.conf>
41 B<--no-arp>
42     starts script without connection to arp module
44 B<-d> <int> 
45     if verbose level is higher than 7 'v' specified parts can be debugged
47       1 : receiving messages
48       2 : sending messages
49       4 : encrypting/decrypting messages
50       8 : verification if a message complies gosa-si requirements
51      16 : message processing
52      32 : ldap connectivity
53      64 : database status and connectivity
54     128 : main process
55     256 : creation of packages_list_db
56     512 : ARP debug information
58 B<-x> <dump>
59      dump configuration to stdout
60      ( 1 = current, 2 = default )
62 =head1 DESCRIPTION
64 gosa-si-server  belongs  to  the  support infrastructure of GOsa.  Several gosa-si-clients can connect to one gosa-si-server.  The server take care of the message forwarding from GOsa to si-clients.  At the client site each message is related to a working instruction which will be executed there.  Depending of the message an answer from the client to GOsa via the server is possible.  Additional to answers clients reporting events  or  information  to the server.  The server registers himself at other servers in network and shares his knowledge with them.  So messages to clients which are no registrated locally will be forward to the client corresponding server. The communication within the complete SI nework is realised by XML messages.
67 =head1 BUGS 
69 Please report any bugs, or post any suggestions, to the GOsa mailing list <gosa-devel@oss.gonicus.de> or to <https://oss.gonicus.de/labs/gosa>
72 =head1 LICENCE AND COPYRIGHT
74 This code is part of GOsa (L<http://www.gosa-project.org>)
76 Copyright (C) 2003-2010 GONICUS GmbH
78 This program is distributed in the hope that it will be useful,
79 but WITHOUT ANY WARRANTY; without even the implied warranty of
80 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
81 GNU General Public License for more details.
83 =cut
85 use strict;
86 use warnings;
88 use Getopt::Long;
89 use Config::IniFiles;
90 use IO::Socket::INET;
91 use IO::Handle;
92 use IO::Select;
93 use Crypt::Rijndael;
94 use MIME::Base64;
95 use Digest::MD5  qw(md5 md5_hex md5_base64);
96 use XML::Simple;
97 use Data::Dumper;
98 use Sys::Syslog qw( :DEFAULT setlogsock);
99 use Time::HiRes qw( usleep clock_gettime );
100 use File::Spec;
101 use File::Basename;
102 use File::Find;
103 use File::Copy;
104 use File::Path;
105 use Net::LDAP;
106 use Net::LDAP::Util qw(:escape);
107 use File::Pid;
108 use GOsaSI::GosaSupportDaemon;
110 use POE qw(Component::Server::TCP Wheel::Run Filter::Reference);
111 use Symbol qw(qualify_to_ref);
112 use Fcntl qw/:flock/;
113 use POSIX;
115 my $server_version = '$HeadURL: https://oss.gonicus.de/repositories/gosa/trunk/gosa-si/gosa-si-server $:$Rev$';
117 # revision number of server and program name
118 my $server_headURL;
119 my $server_revision;
120 my $server_status;
122 my $db_module = "DBsqlite";
124 no strict "refs";
125 require ("GOsaSI/".$db_module.".pm");
126 ("GOsaSI/".$db_module)->import;
129 my $modules_path = "/usr/lib/gosa-si/modules";
130 use lib "/usr/lib/gosa-si/modules";
132 my ($foreground, $ping_timeout);
133 my ($server);
134 my ($gosa_server, $job_queue_timeout, $job_queue_loop_delay);
135 my ($messaging_db_loop_delay);
136 my $procid;
137 my $arp_fifo;
138 my $debug_parts = 0;
139 my $debug_parts_bitstring;
140 my ($xml);
141 my $sources_list;
142 my $max_clients;
143 my %repo_files=();
144 my $repo_path;
145 my %repo_dirs=();
147 my $server_status_hash = { 'developmental'=>'revision', 'stable'=>'release'};
149 # Variables declared in config file are always set to 'our'
150 our (%cfg_defaults, $log_file, $pid_file, $pid,
151     $server_ip, $server_port, $ClientPackages_key, $dns_lookup,
152     $arp_activ, $gosa_unit_tag,
153     $GosaPackages_key, $gosa_timeout,
154     $serverPackages_enabled, $foreign_server_string, $server_domain, $ServerPackages_key, $foreign_servers_register_delay,
155     $wake_on_lan_passwd, $job_synchronization, $modified_jobs_loop_delay,
156     $arp_enabled, $arp_interface,
157     $opsi_enabled, $opsi_server, $opsi_admin, $opsi_password,
158     $new_systems_ou,
159     $arch,
160 );
162 # additional variable which should be globaly accessable
163 our $server_address;
164 our $server_mac_address;
165 our $gosa_address;
166 our $no_arp;
167 our $forground;
168 our $cfg_file;
169 our ($ldap_uri, $ldap_base, $ldap_admin_dn, $ldap_admin_password, $ldap_server_dn, $ldap_version, $ldap_retry_sec);
170 our ($mysql_username, $mysql_password, $mysql_database, $mysql_host);
171 our $known_modules;
172 our $known_functions;
173 our $root_uid;
174 our $adm_gid;
175 our $verbose= 0;
176 our $global_kernel;
178 # where is the config stored by default and his name
179 our $config = '/etc/gosa-si/server.conf';
181 # by default dumping of config is undefined
182 our $dump_config = undef;
184 # if foreground is not null, script will be not forked to background
185 $foreground = 0 ;
187 # specifies the timeout seconds while checking the online status of a registrating client
188 $ping_timeout = 5;
190 $no_arp = 0;
191 my $packages_list_under_construction = "/tmp/packages_list_creation_in_progress";
192 my @packages_list_statements;
193 my $watch_for_new_jobs_in_progress = 0;
195 # holds all incoming decrypted messages
196 our $incoming_db;
197 our $incoming_tn = 'incoming';
198 my $incoming_file_name;
199 my @incoming_col_names = ("id INTEGER PRIMARY KEY",
200         "timestamp VARCHAR(14) DEFAULT 'none'", 
201         "headertag VARCHAR(255) DEFAULT 'none'",
202         "targettag VARCHAR(255) DEFAULT 'none'",
203         "xmlmessage TEXT",
204         "module VARCHAR(255) DEFAULT 'none'",
205         "sessionid VARCHAR(255) DEFAULT '0'",
206 );
208 # holds all gosa jobs
209 our $job_db;
210 our $job_queue_tn = 'jobs';
211 my $job_queue_file_name;
212 my @job_queue_col_names = ("id INTEGER PRIMARY KEY",
213         "timestamp VARCHAR(14) DEFAULT 'none'", 
214         "status VARCHAR(255) DEFAULT 'none'", 
215         "result TEXT",
216         "progress VARCHAR(255) DEFAULT 'none'",
217         "headertag VARCHAR(255) DEFAULT 'none'",
218         "targettag VARCHAR(255) DEFAULT 'none'", 
219         "xmlmessage TEXT", 
220         "macaddress VARCHAR(17) DEFAULT 'none'",
221         "plainname VARCHAR(255) DEFAULT 'none'",
222         "siserver VARCHAR(255) DEFAULT 'none'",
223         "modified INTEGER DEFAULT '0'",
224         "periodic VARCHAR(6) DEFAULT 'none'",
225 );
227 # holds all other gosa-si-server
228 our $known_server_db;
229 our $known_server_tn = "known_server";
230 my $known_server_file_name;
231 my @known_server_col_names = ("hostname VARCHAR(255)", "macaddress VARCHAR(17)", "status VARCHAR(255)", "hostkey VARCHAR(255)", "loaded_modules TEXT", "timestamp VARCHAR(14)", "update_time VARCHAR(14)");
233 # holds all registrated clients
234 our $known_clients_db;
235 our $known_clients_tn = "known_clients";
236 my $known_clients_file_name;
237 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)");
239 # holds all registered clients at a foreign server
240 our $foreign_clients_db;
241 our $foreign_clients_tn = "foreign_clients"; 
242 my $foreign_clients_file_name;
243 my @foreign_clients_col_names = ("hostname VARCHAR(255)", "macaddress VARCHAR(17)", "regserver VARCHAR(255)", "timestamp VARCHAR(14)");
245 # holds all logged in user at each client 
246 our $login_users_db;
247 our $login_users_tn = "login_users";
248 my $login_users_file_name;
249 my @login_users_col_names = ("client VARCHAR(255)", "user VARCHAR(255)", "timestamp VARCHAR(14)", "regserver VARCHAR(255) DEFAULT 'localhost'");
251 # holds all fai server, the debian release and tag
252 our $fai_server_db;
253 our $fai_server_tn = "fai_server"; 
254 my $fai_server_file_name;
255 our @fai_server_col_names = ("timestamp VARCHAR(14)", "server VARCHAR(255)", "fai_release VARCHAR(255)", "sections VARCHAR(255)", "tag VARCHAR(255)"); 
257 our $fai_release_db;
258 our $fai_release_tn = "fai_release"; 
259 my $fai_release_file_name;
260 our @fai_release_col_names = ("timestamp VARCHAR(14)", "fai_release VARCHAR(255)", "class VARCHAR(255)", "type VARCHAR(255)", "state VARCHAR(255)"); 
262 # holds all packages available from different repositories
263 our $packages_list_db;
264 our $packages_list_tn = "packages_list";
265 my $packages_list_file_name;
266 our @packages_list_col_names = ("distribution VARCHAR(255)", "package VARCHAR(255)", "version VARCHAR(255)", "section VARCHAR(255)", "description TEXT", "template LONGBLOB", "timestamp VARCHAR(14)");
267 my $outdir = "/tmp/packages_list_db";
269 # holds all messages which should be delivered to a user
270 our $messaging_db;
271 our $messaging_tn = "messaging"; 
272 our @messaging_col_names = ("id INTEGER", "subject TEXT", "message_from VARCHAR(255)", "message_to VARCHAR(255)", 
273         "flag VARCHAR(255)", "direction VARCHAR(255)", "delivery_time VARCHAR(255)", "message TEXT", "timestamp VARCHAR(14)" );
274 my $messaging_file_name;
276 # path to directory to store client install log files
277 our $client_fai_log_dir = "/var/log/fai"; 
279 # queue which stores taskes until one of the $max_children children are ready to process the task
280 #my @tasks = qw();
281 my @msgs_to_decrypt = qw();
282 my $max_children = 2;
285 # loop delay for job queue to look for opsi jobs
286 my $job_queue_opsi_delay = 10;
287 our $opsi_client;
288 our $opsi_url;
289  
290 # Lifetime of logged in user information. If no update information comes after n seconds, 
291 # the user is expeceted to be no longer logged in or the host is no longer running. Because
292 # of this, the user is deleted from login_users_db
293 our $logged_in_user_date_of_expiry = 600;
295 # List of month names, used in function daemon_log
296 my @monthnames = ("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec");
298 # List of accepted periodical xml tags related to cpan modul DateTime
299 our $check_periodic = {"months"=>'', "weeks"=>'', "days"=>'', "hours"=>'', "minutes"=>''};
302 %cfg_defaults = (
303 "General" => {
304     "log-file" => [\$log_file, "/var/log/gosa-si/gosa-si-server.log"],
305     "pid-file" => [\$pid_file, "/var/run/gosa-si/gosa-si-server.pid"],
306     },
307 "Server" => {
308     "ip"                    => [\$server_ip, "0.0.0.0"],
309     "port"                  => [\$server_port, "20081"],
310     "known-clients"         => [\$known_clients_file_name, '/var/lib/gosa-si/clients.db' ],
311     "known-servers"         => [\$known_server_file_name, '/var/lib/gosa-si/servers.db'],
312     "incoming"              => [\$incoming_file_name, '/var/lib/gosa-si/incoming.db'],
313     "login-users"           => [\$login_users_file_name, '/var/lib/gosa-si/users.db'],
314     "fai-server"            => [\$fai_server_file_name, '/var/lib/gosa-si/fai_server.db'],
315     "fai-release"           => [\$fai_release_file_name, '/var/lib/gosa-si/fai_release.db'],
316     "packages-list"         => [\$packages_list_file_name, '/var/lib/gosa-si/packages.db'],
317     "messaging"             => [\$messaging_file_name, '/var/lib/gosa-si/messaging.db'],
318     "foreign-clients"       => [\$foreign_clients_file_name, '/var/lib/gosa-si/foreign_clients.db'],
319     "source-list"           => [\$sources_list, '/etc/apt/sources.list'],
320     "repo-path"             => [\$repo_path, '/srv/www/debian'],
321     "debian-arch"           => [\$arch, 'i386'],     
322     "ldap-uri"              => [\$ldap_uri, ""],
323     "ldap-base"             => [\$ldap_base, ""],
324     "ldap-admin-dn"         => [\$ldap_admin_dn, ""],
325     "ldap-admin-password"   => [\$ldap_admin_password, ""],
326     "ldap-version"          => [\$ldap_version, 3],
327     "ldap-retry-sec"        => [\$ldap_retry_sec, 10],
328     "gosa-unit-tag"         => [\$gosa_unit_tag, ""],
329     "max-clients"           => [\$max_clients, 10],
330     "wol-password"          => [\$wake_on_lan_passwd, ""],
331     "mysql-username"        => [\$mysql_username, "gosa_si"],
332     "mysql-password"        => [\$mysql_password, ""],
333     "mysql-database"        => [\$mysql_database, "gosa_si"],
334     "mysql-host"            => [\$mysql_host, "127.0.0.1"],
335     },
336 "GOsaPackages" => {
337     "job-queue" => [\$job_queue_file_name, '/var/lib/gosa-si/jobs.db'],
338     "job-queue-loop-delay" => [\$job_queue_loop_delay, 3],
339     "messaging-db-loop-delay" => [\$messaging_db_loop_delay, 3],
340     "key" => [\$GosaPackages_key, "none"],
341     "new-systems-ou" => [\$new_systems_ou, 'ou=workstations,ou=systems'],
342     },
343 "ClientPackages" => {
344     "key" => [\$ClientPackages_key, "none"],
345     "user-date-of-expiry" => [\$logged_in_user_date_of_expiry, 600],
346     },
347 "ServerPackages"=> {
348     "enabled"      => [\$serverPackages_enabled, "true"],
349     "address"      => [\$foreign_server_string, ""],
350     "dns-lookup"   => [\$dns_lookup, "true"],
351     "domain"       => [\$server_domain, ""],
352     "key"          => [\$ServerPackages_key, "none"],
353     "key-lifetime" => [\$foreign_servers_register_delay, 120],
354     "job-synchronization-enabled" => [\$job_synchronization, "true"],
355     "synchronization-loop" => [\$modified_jobs_loop_delay, 5],
356     },
357 "ArpHandler" => {
358     "enabled"   => [\$arp_enabled, "false"],
359     "interface" => [\$arp_interface, "all"],
360         },
361 "Opsi" => {
362     "enabled"  => [\$opsi_enabled, "false"], 
363     "server"   => [\$opsi_server, "localhost"],
364     "admin"    => [\$opsi_admin, "opsi-admin"],
365     "password" => [\$opsi_password, "secret"],
366    },
368 );
370 #############################
372 # @brief Display error message and/or help text.
374 # In correspondence to previous GetOptions
376 # @param $text - string to print as error message
377 # @param $help - set true, if you want to show usage help
379 sub usage
381   my( $text, $help ) = @_;
383   $text = undef if( 'h' eq $text );
384   (defined $text) && print STDERR "\n$text\n";
386   if( (defined $help && $help)
387       || (!defined $help && !defined $text) )
389     print STDERR << "EOF";
391   usage: $0 [-hvf] [-c config] [-d number]
393    -h        : this (help) message
394    -c <file> : config file (default: ${config})
395    -x <cfg>  : dump configuration to stdout
396              ( 1 = current, 2 = default )
397    -f        : foreground (don't fork)
398            -v        : be verbose (multiple to increase verbosity)
399                               'v': error logs
400                             'vvv': warning plus error logs                                              
401                           'vvvvv': info plus warning logs
402                         'vvvvvvv': debug plus info logs
403            -no-arp   : starts gosa-si-server without connection to arp module
404            -d <int>  : if verbose level is higher than 7x 'v' specified parts can be debugged
405                            1 : report incoming messages
406                            2 : report unencrypted outgoing messages 
407                            4 : report encrypting key for messages
408                            8 : report decrypted incoming message and verification if the message complies gosa-si requirements
409                           16 : message processing
410                           32 : ldap connectivity
411                           64 : database status and connectivity
412                          128 : main process 
413                          256 : creation of packages_list_db
414                          512 : ARP debug information
415 EOF
417   print( "\n" );
419   exit( -1 );
422 #############################
424 # @brief Manage gosa-si-client configuration.
426 # Will exit after successfull dump to stdout (type = 1 | 2)
428 # Dump type can be:
429 #   1: Current gosa-si-client configuration in config file (exit)
430 #   2: Default gosa-si-client configuration (exit)
431 #   3: Dump to logfile (no exit)
433 # @param int config type
435 sub dump_configuration {
437   my( $cfg_type ) = @_;
439   return if( ! defined $cfg_type );
441   if(1==$cfg_type ) {
442     print( "# Current gosa-si-server configuration\n" );
443         } elsif (2==$cfg_type) {
444     print( "# Default gosa-si-server configuration\n" );
445         } elsif (3==$cfg_type) {
446     daemon_log( "Dumping gosa-si-server configuration\n", 2 );
447         } else {
448     return;
449         }
451   foreach my $section (keys %cfg_defaults) {
452     if( 3 != $cfg_type ) { 
453       print( "\n[${section}]\n" ); 
454         } else {
455       daemon_log( "\n  [${section}]\n", 3 ); 
456         }
458                 foreach my $param (sort( keys %{$cfg_defaults{ $section }})) {
459       my $pinfo = $cfg_defaults{ $section }{ $param };
460       my $value;
461       if (1==$cfg_type) {
462         if( defined( ${@$pinfo[ 0 ]} ) ) {
463           $value = ${@$pinfo[ 0 ]};
464           print( "$param=$value\n" );
465                                 } else {
466           print( "#${param}=\n" ); 
467                                 }
468                         } elsif (2==$cfg_type) {
469         $value = @{$pinfo}[ 1 ];
470         if( defined( @$pinfo[ 1 ] ) ) {
471           $value = @{$pinfo}[ 1 ];
472           print( "$param=$value\n" );
473                                 } else {
474           print( "#${param}=\n" ); 
475                                 }
476                         } elsif (3==$cfg_type) {
477         if( defined(  ${@$pinfo[ 0 ]} ) ) {
478           $value = ${@$pinfo[ 0 ]};
479           daemon_log( "  $param=$value\n", 3 )
480                                 }
481                         }
482                 }
483         }
486 # We just exit at stdout dump
487   if( 3 == $cfg_type ) { 
488     daemon_log( "\n", 3 );
489         } else {
490     exit( 0 );
491         }
495 #===  FUNCTION  ================================================================
496 #         NAME:  logging
497 #   PARAMETERS:  level - string - default 'info'
498 #                msg - string -
499 #                facility - string - default 'LOG_DAEMON'
500 #      RETURNS:  nothing
501 #  DESCRIPTION:  function for logging
502 #===============================================================================
503 sub daemon_log {
504     my( $msg, $level ) = @_;
505     if (not defined $msg) { return }
506     if (not defined $level) { $level = 1 }
507         my $to_be_logged = 0;
509         # Write log line if line level is lower than verbosity given in commandline
510         if ($level <= $verbose) 
511         { 
512                 $to_be_logged = 1 ;
513         }
515         # Write if debug flag is set and bitstring matches
516         if ($debug_parts > 0)
517         {
518                 my $tmp_level = ($level - 10 >= 0) ? $level - 10 : 0 ;
519                 my $tmp_level_bitstring = unpack("B32", pack("N", $tmp_level));
520                 if (int($debug_parts_bitstring & $tmp_level_bitstring)) 
521                 {
522                         $to_be_logged = 1;
523                 }
524         }
526         if ($to_be_logged) 
527         {
528                 if(defined $log_file){
529                         my $open_log_fh = sysopen(LOG_HANDLE, $log_file, O_WRONLY | O_CREAT | O_APPEND , 0440);
530                         if(not $open_log_fh) {
531                                 print STDERR "cannot open $log_file: $!";
532                                 return;
533                         }
534                         # Check owner and group of log_file and update settings if necessary
535                         my ($dev, $ino, $mode, $nlink, $uid, $gid, $rdev, $size, $atime, $mtime, $ctime, $blksize, $blocks) = stat($log_file);
536                         if((not $uid eq $root_uid) || (not $gid eq $adm_gid)) {
537                                 chown($root_uid, $adm_gid, $log_file);
538                         }
540                         # Prepare time string for log message
541                         my ($seconds,$minutes,$hours,$monthday,$month,$year,$weekday,$yearday,$sommertime) = localtime(time);
542                         $hours = $hours < 10 ? $hours = "0".$hours : $hours;
543                         $minutes = $minutes < 10 ? $minutes = "0".$minutes : $minutes;
544                         $seconds = $seconds < 10 ? $seconds = "0".$seconds : $seconds;
545                         $month = $monthnames[$month];
546                         $monthday = $monthday < 10 ? $monthday = "0".$monthday : $monthday;
547                         $year+=1900;
549                         # Microseconds since epoch
550                         my $microSeconds = sprintf("%.2f", &Time::HiRes::clock_gettime());
551                         $microSeconds =~ s/^\d*(.\d\d)$/$1/;
553                         # Build log message and write it to log file and commandline
554                         chomp($msg);
555                         my $log_msg = "$month $monthday $hours:$minutes:$seconds$microSeconds $0 $msg\n";
556                         flock(LOG_HANDLE, LOCK_EX);
557                         seek(LOG_HANDLE, 0, 2);
558                         print LOG_HANDLE $log_msg;
559                         flock(LOG_HANDLE, LOCK_UN);
560                         if( $foreground ) 
561                         { 
562                                 print STDERR $log_msg;
563                         }
564                         close( LOG_HANDLE );
565                 }
566         }
570 #===  FUNCTION  ================================================================
571 #         NAME:  check_cmdline_param
572 #   PARAMETERS:  nothing
573 #      RETURNS:  nothing
574 #  DESCRIPTION:  validates commandline parameter
575 #===============================================================================
576 sub check_cmdline_param () {
577     my $err_counter = 0;
579         # Check configuration file
580         if(not defined($cfg_file)) {
581                 $cfg_file = "/etc/gosa-si/server.conf";
582                 if(! -r $cfg_file) {
583                         print STDERR "Please specify a config file.\n";
584                         $err_counter++;
585                 }
586     }
588         # Prepare identification which gosa-si parts should be debugged and which not
589         if (defined $debug_parts) 
590         {
591                 if ($debug_parts =~ /^\d+$/)
592                 {
593                         $debug_parts_bitstring = unpack("B32", pack("N", $debug_parts));
594                 }
595                 else
596                 {
597                         print STDERR "Value '$debug_parts' invalid for option d (number expected)\n";
598                         $err_counter++;
599                 }
600         }
602         # Exit if an error occour
603     if( $err_counter > 0 ) { &usage( "", 1 ); }
607 #===  FUNCTION  ================================================================
608 #         NAME:  check_pid
609 #   PARAMETERS:  nothing
610 #      RETURNS:  nothing
611 #  DESCRIPTION:  handels pid processing
612 #===============================================================================
613 sub check_pid {
614     $pid = -1;
615     # Check, if we are already running
616     if( open( my $LOCK_FILE, "<", "$pid_file" ) ) {
617         $pid = <$LOCK_FILE>;
618         if( defined $pid ) {
619             chomp( $pid );
620             if( -f "/proc/$pid/stat" ) {
621                 my($stat) = `cat /proc/$pid/stat` =~ m/$pid \((.+)\).*/;
622                 if( $stat ) {
623                                         print STDERR "\nERROR: Already running!\n";
624                     close( $LOCK_FILE );
625                     exit -1;
626                 }
627             }
628         }
629         close( $LOCK_FILE );
630         unlink( $pid_file );
631     }
633     # create a syslog msg if it is not to possible to open PID file
634     if (not sysopen(my $LOCK_FILE, $pid_file, O_WRONLY|O_CREAT|O_EXCL, 0644)) {
635         my($msg) = "Couldn't obtain lockfile '$pid_file' ";
636         if (open(my $LOCK_FILE, '<', $pid_file)
637                 && ($pid = <$LOCK_FILE>))
638         {
639             chomp($pid);
640             $msg .= "(PID $pid)\n";
641         } else {
642             $msg .= "(unable to read PID)\n";
643         }
644         if( ! ($foreground) ) {
645             openlog( $0, "cons,pid", "daemon" );
646             syslog( "warning", $msg );
647             closelog();
648         }
649         else {
650             print( STDERR " $msg " );
651         }
652         exit( -1 );
653     }
656 #===  FUNCTION  ================================================================
657 #         NAME:  import_modules
658 #   PARAMETERS:  module_path - string - abs. path to the directory the modules 
659 #                are stored
660 #      RETURNS:  nothing
661 #  DESCRIPTION:  each file in module_path which ends with '.pm' and activation 
662 #                state is on is imported by "require 'file';"
663 #===============================================================================
664 sub import_modules {
665     if (not -e $modules_path) {
666         daemon_log("0 ERROR: cannot find directory or directory is not readable: $modules_path", 1);   
667     }
669     opendir (DIR, $modules_path) or die "ERROR while loading modules from directory $modules_path : $!\n";
671     while (defined (my $file = readdir (DIR))) {
672         if (not $file =~ /(\S*?).pm$/) {
673             next;
674         }
675                 my $mod_name = $1;
677         # ArpHandler switch
678         if( $file =~ /ArpHandler.pm/ ) {
679             if( $arp_enabled eq "false" ) { next; }
680         }
682                 # ServerPackages switch
683                 if ($file eq "ServerPackages.pm" && $serverPackages_enabled eq "false") 
684                 {
685                         $dns_lookup = "false";
686                         next; 
687                 }
688         
689         eval { require $file; };
690         if ($@) {
691             daemon_log("0 ERROR: gosa-si-server could not load module $file", 1);
692             daemon_log("$@", 1);
693             exit;
694                 } else {
695                         my $info = eval($mod_name.'::get_module_info()');
696                         # Only load module if get_module_info() returns a non-null object
697                         if( $info ) {
698                                 my ($input_address, $input_key, $event_hash) = @{$info};
699                                 $known_modules->{$mod_name} = $info;
700                                 daemon_log("0 INFO: module $mod_name loaded", 5);
701                         }
702                 }
703     }   
704     close (DIR);
707 #===  FUNCTION  ================================================================
708 #         NAME:  password_check
709 #   PARAMETERS:  nothing
710 #      RETURNS:  nothing
711 #  DESCRIPTION:  escalates an critical error if two modules exist which are avaialable by 
712 #                the same password
713 #===============================================================================
714 sub password_check {
715     my $passwd_hash = {};
716     while (my ($mod_name, $mod_info) = each %$known_modules) {
717         my $mod_passwd = @$mod_info[1];
718         if (not defined $mod_passwd) { next; }
719         if (not exists $passwd_hash->{$mod_passwd}) {
720             $passwd_hash->{$mod_passwd} = $mod_name;
722         # escalates critical error
723         } else {
724             &daemon_log("0 ERROR: two loaded modules do have the same password. Please modify the 'key'-parameter in config file");
725             &daemon_log("0 ERROR: module='$mod_name' and module='".$passwd_hash->{$mod_passwd}."'");
726             exit( -1 );
727         }
728     }
733 #===  FUNCTION  ================================================================
734 #         NAME:  sig_int_handler
735 #   PARAMETERS:  signal - string - signal came from system
736 #      RETURNS:  nothing
737 #  DESCRIPTION:  handle tasks to be done before signal becomes active
738 #===============================================================================
739 sub sig_int_handler {
740     my ($signal) = @_;
742 #       if (defined($ldap_handle)) {
743 #               $ldap_handle->disconnect;
744 #       }
745     # TODO alle verbliebenden ldap verbindungen aus allen heaps beenden
746     
747     daemon_log("shutting down gosa-si-server", 1);
749                 $global_kernel->yield(TCP_SERVER => 'shutdown');
751                 # to be removed rather crude !!
752                 #system("kill `ps -C gosa-si-server -o pid=`");
754                 $pid->remove or warn "Could not remove $pid_file\n";
756                 exit(0);
758 $SIG{INT} = \&sig_int_handler;
761 sub check_key_and_xml_validity {
762     my ($crypted_msg, $module_key, $session_id) = @_;
763     my $msg;
764     my $msg_hash;
765     my $error_string;
766     eval{
767         $msg = &decrypt_msg($crypted_msg, $module_key);
769         if ($msg =~ /<xml>/i){
770             $msg =~ s/\s+/ /g;  # just for better daemon_log
771             daemon_log("$session_id DEBUG: decrypted_msg: \n$msg", 18);
772             $msg_hash = $xml->XMLin($msg, ForceArray=>1);
774             ##############
775             # check header
776             if( not exists $msg_hash->{'header'} ) { die "no header specified"; }
777             my $header_l = $msg_hash->{'header'};
778             if( (1 > @{$header_l}) || ( ( 'HASH' eq ref @{$header_l}[0]) && (1 > keys %{@{$header_l}[0]}) ) ) { die 'empty header tag'; }
779             if( 1 < @{$header_l} ) { die 'more than one header specified'; }
780             my $header = @{$header_l}[0];
781             if( 0 == length $header) { die 'empty string in header tag'; }
783             ##############
784             # check source
785             if( not exists $msg_hash->{'source'} ) { die "no source specified"; }
786             my $source_l = $msg_hash->{'source'};
787             if( (1 > @{$source_l}) || ( ( 'HASH' eq ref @{$source_l}[0]) && (1 > keys %{@{$source_l}[0]}) ) ) { die 'empty source tag'; }
788             if( 1 < @{$source_l} ) { die 'more than one source specified'; }
789             my $source = @{$source_l}[0];
790             if( 0 == length $source) { die 'source error'; }
792             ##############
793             # check target
794             if( not exists $msg_hash->{'target'} ) { die "no target specified"; }
795             my $target_l = $msg_hash->{'target'};
796             if( (1 > @{$target_l}) || ( ('HASH' eq ref @{$target_l}[0]) && (1 > keys %{@{$target_l}[0]}) ) ) { die 'empty target tag'; }
797         }
798     };
799     if($@) {
800         daemon_log("$session_id ERROR: do not understand the message: $@", 1);
801         $msg = undef;
802         $msg_hash = undef;
803     }
805     return ($msg, $msg_hash);
809 sub check_outgoing_xml_validity {
810     my ($msg, $session_id) = @_;
812     my $msg_hash;
813     eval{
814         $msg_hash = $xml->XMLin($msg, ForceArray=>1);
816         ##############
817         # check header
818         my $header_l = $msg_hash->{'header'};
819         if( 1 != @{$header_l} ) {
820             die 'no or more than one headers specified';
821         }
822         my $header = @{$header_l}[0];
823         if( 0 == length $header) {
824             die 'header has length 0';
825         }
827         ##############
828         # check source
829         my $source_l = $msg_hash->{'source'};
830         if( 1 != @{$source_l} ) {
831             die 'no or more than 1 sources specified';
832         }
833         my $source = @{$source_l}[0];
834         if( 0 == length $source) {
835             die 'source has length 0';
836         }
838                 # Check if source contains hostname instead of ip address
839                 if($source =~ /^[a-z][\w\-\.]+:\d+$/i) {
840                         my ($hostname,$port) = split(/:/, $source);
841                         my $ip_address = inet_ntoa(scalar gethostbyname($hostname));
842                         if(defined($ip_address) && $ip_address =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/ && $port =~ /^\d+$/) {
843                                 # Write ip address to $source variable
844                                 $source = "$ip_address:$port";
845                                 $msg_hash->{source}[0] = $source ;
846                                 $msg =~ s/<source>.*<\/source>/<source>$source<\/source>/; 
847                         }
848                 }
849         unless( $source =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
850                 $source =~ /^GOSA$/i) {
851             die "source '$source' is neither a complete ip-address with port nor 'GOSA'";
852         }
853         
854         ##############
855         # check target  
856         my $target_l = $msg_hash->{'target'};
857         if( 0 == @{$target_l} ) {
858             die "no targets specified";
859         }
860         foreach my $target (@$target_l) {
861             if( 0 == length $target) {
862                 die "target has length 0";
863             }
864             unless( $target =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
865                     $target =~ /^GOSA$/i ||
866                     $target =~ /^\*$/ ||
867                     $target =~ /KNOWN_SERVER/i ||
868                     $target =~ /JOBDB/i ||
869                     $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 ){
870                 die "target '$target' is not a complete ip-address with port or a valid target name or a mac-address";
871             }
872         }
873     };
874     if($@) {
875         daemon_log("$session_id ERROR: outgoing msg is not gosa-si envelope conform: $@", 1);
876         daemon_log("$@ ".(defined($msg) && length($msg)>0)?$msg:"Empty Message", 1);
877         $msg_hash = undef;
878     }
880     return ($msg, $msg_hash);
884 sub input_from_known_server {
885     my ($input, $remote_ip, $session_id) = @_ ;  
886     my ($msg, $msg_hash, $module);
888     my $sql_statement= "SELECT * FROM known_server";
889     my $query_res = $known_server_db->select_dbentry( $sql_statement ); 
891     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
892         my $host_name = $hit->{hostname};
893         if( not $host_name =~ "^$remote_ip") {
894             next;
895         }
896         my $host_key = $hit->{hostkey};
897         daemon_log("$session_id DEBUG: input_from_known_server: host_name: $host_name", 14);
898         daemon_log("$session_id DEBUG: input_from_known_server: host_key: $host_key", 14);
900         # check if module can open msg envelope with module key
901         my ($tmp_msg, $tmp_msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
902         if( (!$tmp_msg) || (!$tmp_msg_hash) ) {
903             daemon_log("$session_id DEBUG: input_from_known_server: deciphering raise error", 14);
904             daemon_log("$@", 14);
905             next;
906         }
907         else {
908             $msg = $tmp_msg;
909             $msg_hash = $tmp_msg_hash;
910             $module = "ServerPackages";
911             daemon_log("$session_id DEBUG: check_key_and_xml_validity... ok", 14);
912             last;
913         }
914     }
916     if( (!$msg) || (!$msg_hash) || (!$module) ) {
917         daemon_log("$session_id DEBUG: Incoming message is not from a known server", 14);
918     }
919   
920     return ($msg, $msg_hash, $module);
924 sub input_from_known_client {
925     my ($input, $remote_ip, $session_id) = @_ ;  
926     my ($msg, $msg_hash, $module);
928     my $sql_statement= "SELECT * FROM known_clients";
929     my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
930     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
931         my $host_name = $hit->{hostname};
932         if( not $host_name =~ /^$remote_ip/) {
933                 next;
934                 }
935         my $host_key = $hit->{hostkey};
936         &daemon_log("$session_id DEBUG: input_from_known_client: host_name: $host_name", 14);
937         &daemon_log("$session_id DEBUG: input_from_known_client: host_key: $host_key", 14);
939         # check if module can open msg envelope with module key
940         ($msg, $msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
942         if( (!$msg) || (!$msg_hash) ) {
943             &daemon_log("$session_id DEGUG: input_from_known_client: deciphering raise error", 14);
944             next;
945         }
946         else {
947             $module = "ClientPackages";
948             daemon_log("$session_id DEBUG: check_key_and_xml_validity... ok", 14);
949             last;
950         }
951     }
953     if( (!$msg) || (!$msg_hash) || (!$module) ) {
954         &daemon_log("$session_id DEBUG: Incoming message is not from a known client", 14);
955     }
957     return ($msg, $msg_hash, $module);
961 sub input_from_unknown_host {
962         no strict "refs";
963         my ($input, $session_id) = @_ ;
964         my ($msg, $msg_hash, $module);
965         my $error_string;
967         my %act_modules = %$known_modules;
969         while( my ($mod, $info) = each(%act_modules)) {
971                 # check a key exists for this module
972                 my $module_key = ${$mod."_key"};
973                 if( not defined $module_key ) {
974                         if( $mod eq 'ArpHandler' ) {
975                                 next;
976                         }
977                         daemon_log("$session_id ERROR: no key specified in config file for $mod", 1);
978                         next;
979                 }
980                 daemon_log("$session_id DEBUG: $mod: $module_key", 14);
982                 # check if module can open msg envelope with module key
983                 ($msg, $msg_hash) = &check_key_and_xml_validity($input, $module_key, $session_id);
984                 if( (not defined $msg) || (not defined $msg_hash) ) {
985                         next;
986                 } else {
987                         $module = $mod;
988             daemon_log("$session_id DEBUG: check_key_and_xml_validity... ok", 18);
989                         last;
990                 }
991         }
993         if( (!$msg) || (!$msg_hash) || (!$module)) {
994                 daemon_log("$session_id DEBUG: Incoming message is not from an unknown host", 14);
995         }
997         return ($msg, $msg_hash, $module);
1001 sub create_ciphering {
1002     my ($passwd) = @_;
1003         if((!defined($passwd)) || length($passwd)==0) {
1004                 $passwd = "";
1005         }
1006     $passwd = substr(md5_hex("$passwd") x 32, 0, 32);
1007     my $iv = substr(md5_hex('GONICUS GmbH'),0, 16);
1008     my $my_cipher = Crypt::Rijndael->new($passwd , Crypt::Rijndael::MODE_CBC());
1009     $my_cipher->set_iv($iv);
1010     return $my_cipher;
1014 sub encrypt_msg {
1015     my ($msg, $key) = @_;
1016     my $my_cipher = &create_ciphering($key);
1017     my $len;
1018     {
1019             use bytes;
1020             $len= 16-length($msg)%16;
1021     }
1022     $msg = "\0"x($len).$msg;
1023     $msg = $my_cipher->encrypt($msg);
1024     chomp($msg = &encode_base64($msg));
1025     # there are no newlines allowed inside msg
1026     $msg=~ s/\n//g;
1027     return $msg;
1031 sub decrypt_msg {
1033     my ($msg, $key) = @_ ;
1034     $msg = &decode_base64($msg);
1035     my $my_cipher = &create_ciphering($key);
1036     $msg = $my_cipher->decrypt($msg); 
1037     $msg =~ s/\0*//g;
1038     return $msg;
1042 sub get_encrypt_key {
1043     my ($target) = @_ ;
1044     my $encrypt_key;
1045     my $error = 0;
1047     # target can be in known_server
1048     if( not defined $encrypt_key ) {
1049         my $sql_statement= "SELECT * FROM known_server WHERE hostname='$target'";
1050         my $query_res = $known_server_db->select_dbentry( $sql_statement ); 
1051         while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1052             my $host_name = $hit->{hostname};
1053             if( $host_name ne $target ) {
1054                 next;
1055             }
1056             $encrypt_key = $hit->{hostkey};
1057             last;
1058         }
1059     }
1061     # target can be in known_client
1062     if( not defined $encrypt_key ) {
1063         my $sql_statement= "SELECT * FROM known_clients WHERE hostname='$target'";
1064         my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
1065         while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1066             my $host_name = $hit->{hostname};
1067             if( $host_name ne $target ) {
1068                 next;
1069             }
1070             $encrypt_key = $hit->{hostkey};
1071             last;
1072         }
1073     }
1075     return $encrypt_key;
1079 #===  FUNCTION  ================================================================
1080 #         NAME:  open_socket
1081 #   PARAMETERS:  PeerAddr string something like 192.168.1.1 or 192.168.1.1:10000
1082 #                [PeerPort] string necessary if port not appended by PeerAddr
1083 #      RETURNS:  socket IO::Socket::INET
1084 #  DESCRIPTION:  open a socket to PeerAddr
1085 #===============================================================================
1086 sub open_socket {
1087     my ($PeerAddr, $PeerPort) = @_ ;
1088     if(defined($PeerPort)){
1089         $PeerAddr = $PeerAddr.":".$PeerPort;
1090     }
1091     my $socket;
1092     $socket = new IO::Socket::INET(PeerAddr => $PeerAddr,
1093             Porto => "tcp",
1094             Type => SOCK_STREAM,
1095             Timeout => 5,
1096             );
1097     if(not defined $socket) {
1098         return;
1099     }
1100 #    &daemon_log("DEBUG: open_socket: $PeerAddr", 7);
1101     return $socket;
1105 sub send_msg_to_target {
1106     my ($msg, $address, $encrypt_key, $msg_header, $session_id) = @_ ;
1107     my $error = 0;
1108     my $header;
1109     my $timestamp = &get_time();
1110     my $new_status;
1111     my $act_status;
1112     my ($sql_statement, $res);
1113   
1114     if( $msg_header ) {
1115         $header = "'$msg_header'-";
1116     } else {
1117         $header = "";
1118     }
1120         # Memorize own source address
1121         my $own_source_address = &get_local_ip_for_remote_ip(sprintf("%s", $address =~ /^([0-9\.]*?):.*$/));
1122         $own_source_address .= ":".$server_port;
1124         # Patch 0.0.0.0 source to real address
1125         $msg =~ s/<source>(0\.0\.0\.0):(\d*?)<\/source>/<source>$own_source_address<\/source>/s;
1126         # Patch GOSA source to real address and add forward_to_gosa tag
1127         $msg =~ s/<source>GOSA<\/source>/<source>$own_source_address<\/source> <forward_to_gosa>$own_source_address,$session_id<\/forward_to_gosa>/ ;
1129     # encrypt xml msg
1130     my $crypted_msg = &encrypt_msg($msg, $encrypt_key);
1132     # opensocket
1133     my $socket = &open_socket($address);
1134     if( !$socket ) {
1135         daemon_log("$session_id ERROR: Cannot open socket to host '$address'. Message processing aborted!", 1);
1136         $error++;
1137     }
1138     
1139     if( $error == 0 ) {
1140         # send xml msg
1141         print $socket $crypted_msg.";$own_source_address\n";
1142         daemon_log("$session_id INFO: send ".$header."msg to $address", 5);
1143         daemon_log("$session_id DEBUG: message:\n$msg", 12);
1144         
1145     }
1147     # close socket in any case
1148     if( $socket ) {
1149         close $socket;
1150     }
1152     if( $error > 0 ) { $new_status = "down"; }
1153     else { $new_status = $msg_header; }
1156     # known_clients
1157     $sql_statement = "SELECT * FROM $known_clients_tn WHERE hostname='$address'";
1158     $res = $known_clients_db->select_dbentry($sql_statement);
1159     if( keys(%$res) == 1) {
1160         $act_status = exists $res->{1}->{'status'} ? $res->{1}->{'status'} : "";
1161         if ($act_status eq "down" && $new_status eq "down") {
1162             $sql_statement = "DELETE FROM known_clients WHERE hostname='$address'";
1163             $res = $known_clients_db->del_dbentry($sql_statement);
1164             daemon_log("$session_id WARNING: failed 2x to send msg to host '$address', delete host from known_clients", 3);
1165         } else { 
1166             $sql_statement = "UPDATE known_clients SET status='$new_status', timestamp='$timestamp' WHERE hostname='$address'";
1167             $res = $known_clients_db->update_dbentry($sql_statement);
1168             if($new_status eq "down"){
1169                 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
1170             } else {
1171                 daemon_log("$session_id DEBUG: set '$address' from status '$act_status' to '$new_status'", 138);
1172             }
1173         }
1174     }
1176     # known_server
1177     $sql_statement = "SELECT * FROM $known_server_tn WHERE hostname='$address'";
1178     $res = $known_server_db->select_dbentry($sql_statement);
1179     if( keys(%$res) == 1) {
1180         $act_status = exists $res->{1}->{'status'} ? $res->{1}->{'status'} : "";
1181         if ($act_status eq "down" && $new_status eq "down") {
1182             $sql_statement = "DELETE FROM known_server WHERE hostname='$address'";
1183             $res = $known_server_db->del_dbentry($sql_statement);
1184             daemon_log("$session_id WARNING: failed 2x to send a message to host '$address', delete host from known_server", 3);
1185         } 
1186         else { 
1187             $sql_statement = "UPDATE known_server SET status='$new_status', timestamp='$timestamp' WHERE hostname='$address'";
1188             $res = $known_server_db->update_dbentry($sql_statement);
1189             if($new_status eq "down"){
1190                 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
1191             } else {
1192                 daemon_log("$session_id DEBUG: set '$address' from status '$act_status' to '$new_status'", 138);
1193             }
1194         }
1195     }
1196     return $error; 
1200 sub update_jobdb_status_for_send_msgs {
1201     my ($session_id, $answer, $error) = @_;
1202     if( $answer =~ /<jobdb_id>(\d+)<\/jobdb_id>/ ) {
1203                 &daemon_log("$session_id DEBUG: try to update job status", 138); 
1204         my $jobdb_id = $1;
1205     
1206         $answer =~ /<header>(.*)<\/header>/;
1207         my $job_header = $1;
1209         $answer =~ /<target>(.*)<\/target>/;
1210         my $job_target = $1;
1211             
1212         # Sending msg failed
1213         if( $error ) {
1215             # Set jobs to done, jobs do not need to deliver their message in any case
1216             if (($job_header eq "trigger_action_localboot")
1217                     ||($job_header eq "trigger_action_lock")
1218                     ||($job_header eq "trigger_action_halt") 
1219                     ) {
1220                 my $sql_statement = "UPDATE $job_queue_tn SET status='done' WHERE id=$jobdb_id";
1221                 my $res = $job_db->update_dbentry($sql_statement);
1222                 
1223             # Reactivate jobs, jobs need to deliver their message
1224             } elsif (($job_header eq "trigger_action_activate")
1225                     ||($job_header eq "trigger_action_update")
1226                     ||($job_header eq "trigger_action_reinstall") 
1227                     ||($job_header eq "trigger_activate_new")
1228                     ) {
1229                                                 if ($job_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) {
1230                                                         &reactivate_job_with_delay($session_id, $job_target, $job_header, 30 );
1231                                                 } else {
1232                                                         # If we don't have the mac adress at this time, we use the plainname
1233                                                         my $plainname_result = $job_db->select_dbentry("SELECT plainname from jobs where id=$jobdb_id");
1234                                                         my $plainname = $job_target;
1235                                                         if ((keys(%$plainname_result) > 0) ) {
1236                                                                 $plainname = $plainname_result->{1}->{$job_target};
1237                                                         }
1238                                                         &reactivate_job_with_delay($session_id, $plainname, $job_header, 30 );
1239                                                 }
1240             # For all other messages
1241             } else {
1242                 my $sql_statement = "UPDATE $job_queue_tn ".
1243                     "SET status='error', result='can not deliver msg, please consult log file' ".
1244                     "WHERE id=$jobdb_id";
1245                 my $res = $job_db->update_dbentry($sql_statement);
1246             }
1248         # Sending msg was successful
1249         } else {
1250             # Set jobs localboot, lock, activate, halt, reboot and wake to done
1251             # jobs reinstall, update, inst_update do themself setting to done
1252             if (($job_header eq "trigger_action_localboot")
1253                     ||($job_header eq "trigger_action_lock")
1254                     ||($job_header eq "trigger_action_activate")
1255                     ||($job_header eq "trigger_action_halt") 
1256                     ||($job_header eq "trigger_action_reboot")
1257                     ||($job_header eq "trigger_action_wake")
1258                     ||($job_header eq "trigger_wake")
1259                     ) {
1261                 my $sql_statement = "UPDATE $job_queue_tn ".
1262                     "SET status='done' ".
1263                     "WHERE id=$jobdb_id AND status='processed'";
1264                 my $res = $job_db->update_dbentry($sql_statement);
1265             } else { 
1266                 &daemon_log("$session_id DEBUG: sending message succeed but cannot update job status.", 138); 
1267             } 
1268         } 
1269     } else { 
1270         &daemon_log("$session_id DEBUG: cannot update job status, msg has no jobdb_id-tag.", 138); 
1271     }
1274 sub reactivate_job_with_delay {
1275     my ($session_id, $target, $header, $delay) = @_ ;
1276     # Sometimes the client is still booting or does not wake up, in this case reactivate the job (if it exists) with a delay of n sec
1277     
1278     if (not defined $delay) { $delay = 30 } ;
1279     my $delay_timestamp = &calc_timestamp(&get_time(), "plus", $delay);
1281     my $sql = "UPDATE $job_queue_tn Set timestamp='$delay_timestamp', status='waiting' WHERE (macaddress LIKE '$target' OR plainname LIKE '$target') AND headertag='$header'"; 
1282     my $res = $job_db->update_dbentry($sql);
1283     daemon_log("$session_id INFO: '$header'-job will be reactivated at '$delay_timestamp' ".
1284             "cause client '$target' is currently not available", 5);
1285     return;
1289 sub sig_handler {
1290         my ($kernel, $signal) = @_[KERNEL, ARG0] ;
1291         daemon_log("0 INFO got signal '$signal'", 1); 
1292         $kernel->sig_handled();
1293         return;
1297 sub msg_to_decrypt {
1298         my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
1299         my $session_id = $session->ID;
1300         my ($msg, $msg_hash, $module);
1301         my $error = 0;
1303         # fetch new msg out of @msgs_to_decrypt
1304         my $tmp_next_msg = shift @msgs_to_decrypt;
1305     my ($next_msg, $msg_source) = split(/;/, $tmp_next_msg);
1307         # msg is from a new client or gosa
1308         ($msg, $msg_hash, $module) = &input_from_unknown_host($next_msg, $session_id);
1310         # msg is from a gosa-si-server
1311         if(((!$msg) || (!$msg_hash) || (!$module)) && ($serverPackages_enabled eq "true")){
1312                 if (not defined $msg_source) 
1313                 {
1314                         # Only needed, to be compatible with older gosa-si-server versions
1315                         ($msg, $msg_hash, $module) = &input_from_known_server($next_msg, $heap->{'remote_ip'}, $session_id);
1316                 }
1317                 else
1318                 {
1319                         ($msg, $msg_hash, $module) = &input_from_known_server($next_msg, $msg_source, $session_id);
1320                 }
1321         }
1322         # msg is from a gosa-si-client
1323         if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1324                 if (not defined $msg_source) 
1325                 {
1326                         # Only needed, to be compatible with older gosa-si-server versions
1327                         ($msg, $msg_hash, $module) = &input_from_known_client($next_msg, $heap->{'remote_ip'}, $session_id);
1328                 }
1329                 else
1330                 {
1331                         ($msg, $msg_hash, $module) = &input_from_known_client($next_msg, $msg_source, $session_id);
1332                 }
1333         }
1334         # an error occurred
1335         if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1336                 # If an incoming msg could not be decrypted (maybe a wrong key), decide if msg comes from a client 
1337                 # or a server.  In case of a client, send a ping. If the client could not understand a msg from its 
1338                 # server the client cause a re-registering process. In case of a server, decrease update_time in kown_server_db
1339                 # and trigger a re-registering process for servers
1340                 if (defined $msg_source && $msg_source =~ /:$server_port$/ && $serverPackages_enabled eq "true")
1341                 {
1342                         daemon_log("$session_id WARNING: Cannot understand incoming msg from server '$msg_source'. Cause re-registration process for servers.", 3);
1343                         my $update_statement = "UPDATE $known_server_tn SET update_time='19700101000000' WHERE hostname='$msg_source'"; 
1344                         daemon_log("$session_id DEBUG: $update_statement", 7);
1345                         my $upadte_res = $known_server_db->exec_statement($update_statement);
1346                         $kernel->yield("register_at_foreign_servers");
1347                 }
1348                 elsif ((defined $msg_source) && (not $msg_source =~ /:$server_port$/))
1349                 {
1350                         daemon_log("$session_id WARNING: Cannot understand incoming msg from client '$msg_source'. Send ping-msg to cause a re-registering of the client if necessary", 3);
1351                         #my $remote_ip = $heap->{'remote_ip'};
1352                         #my $remote_port = $heap->{'remote_port'};
1353                         my $ping_msg = "<xml> <header>gosa_ping</header> <source>$server_address</source><target>$msg_source</target></xml>";
1354                         my ($test_error, $test_error_string) = &send_msg_to_target($ping_msg, "$msg_source", "dummy-key", "gosa_ping", $session_id);
1355                         daemon_log("$session_id WARNING: Sending msg to cause re-registering: $ping_msg", 3);
1356                 }
1357                 else
1358                 {
1359                         my $foreign_host = defined $msg_source ? $msg_source : $heap->{'remote_ip'};
1360                         daemon_log("$session_id ERROR: Incoming message from host '$foreign_host' cannot be understood. Processing aborted!", 1);
1361                         daemon_log("$session_id DEBUG: Aborted message: $tmp_next_msg", 11);
1362                 }
1364                 $error++
1365         }
1368         my $header;
1369         my $target;
1370         my $source;
1371         my $done = 0;
1372         my $sql;
1373         my $res;
1375         # check whether this message should be processed here
1376         if ($error == 0) {
1377                 $header = @{$msg_hash->{'header'}}[0];
1378                 $target = @{$msg_hash->{'target'}}[0];
1379                 $source = @{$msg_hash->{'source'}}[0];
1380                 my $not_found_in_known_clients_db = 0;
1381                 my $not_found_in_known_server_db = 0;
1382                 my $not_found_in_foreign_clients_db = 0;
1383                 my $local_address;
1384                 my $local_mac;
1385                 my ($target_ip, $target_port) = split(':', $target);
1387                 # Determine the local ip address if target is an ip address
1388                 if ($target =~ /^\d+\.\d+\.\d+\.\d+:\d+$/) {
1389                         $local_address = &get_local_ip_for_remote_ip($target_ip).":$server_port";
1390                 } else {
1391                         $local_address = $server_address;
1392                 }
1394                 # Determine the local mac address if target is a mac address
1395                 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) {
1396                         my $loc_ip = &get_local_ip_for_remote_ip($heap->{'remote_ip'});
1397                         my $network_interface= &get_interface_for_ip($loc_ip);
1398                         $local_mac = &get_mac_for_interface($network_interface);
1399                 } else {
1400                         $local_mac = $server_mac_address;
1401                 }
1403                 # target and source is equal to GOSA -> process here
1404                 if (not $done) {
1405                         if ($target eq "GOSA" && $source eq "GOSA") {
1406                                 $done = 1;                    
1407                                 &daemon_log("$session_id DEBUG: target and source is 'GOSA' -> process '$header' here", 11);
1408                         }
1409                 }
1411                 # target is own address without forward_to_gosa-tag -> process here
1412                 if (not $done) {
1413                         #if ((($target eq $local_address) || ($target eq $local_mac) ) && (not exists $msg_hash->{'forward_to_gosa'})) {
1414                         if (($target eq $local_address) && (not exists $msg_hash->{'forward_to_gosa'})) {
1415                                 $done = 1;
1416                                 if ($source eq "GOSA") {
1417                                         $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1418                                 }
1419                                 &daemon_log("$session_id DEBUG: target is own address without forward_to_gosa-tag -> process '$header' here", 11);
1420                         }
1421                 }
1423                 # target is own address with forward_to_gosa-tag not pointing to myself -> process here
1424                 if (not $done) {
1425                         my $forward_to_gosa =  @{$msg_hash->{'forward_to_gosa'}}[0];
1426                         my $gosa_at;
1427                         my $gosa_session_id;
1428                         if (($target eq $local_address) && (defined $forward_to_gosa)){
1429                                 my ($gosa_at, $gosa_session_id) = split(/,/, $forward_to_gosa);
1430                                 if ($gosa_at ne $local_address) {
1431                                         $done = 1;
1432                                         &daemon_log("$session_id DEBUG: target is own address with forward_to_gosa-tag not pointing to myself -> process '$header' here", 11); 
1433                                 }
1434                         }
1435                 }
1437                 # Target is a client address and there is a processing function within a plugin -> process loaclly
1438                 if (not $done)
1439                 {
1440                         # Check if target is a client address
1441                         $sql = "SELECT * FROM $known_clients_tn WHERE (hostname='$target' OR macaddress LIKE '$target')"; 
1442                         $res = $known_clients_db->select_dbentry($sql);
1443                         if ((keys(%$res) > 0) ) 
1444                         {
1445                                 my $hostname = $res->{1}->{'hostname'};
1446                                 my $reduced_header = $header;
1447                                 $reduced_header =~ s/gosa_//;
1448                                 # Check if there is a processing function within a plugin
1449                                 if (exists $known_functions->{$reduced_header}) 
1450                                 {
1451                                         $msg =~ s/<target>\S*<\/target>/<target>$hostname<\/target>/;
1452                                         $done = 1;
1453                                         &daemon_log("$session_id DEBUG: Target is client address with processing function within a plugin -> process '$header' here", 11);
1454                                 }
1455                         }
1456                 }
1458                 # If header has a 'job_' prefix, do always process message locally
1459                 # which means put it into job queue
1460                 if ((not $done) && ($header =~ /job_/))
1461                 {
1462                         $done = 1;
1463                         &daemon_log("$session_id DEBUG: Header has a 'job_' prefix. Put it into job queue. -> process '$header' here", 11);
1464                 }
1466                 # if message should be processed here -> add message to incoming_db
1467                 if ($done) {
1468                         # if a 'job_' or a 'gosa_' message comes from a foreign server, fake module from
1469                         # ServerPackages to GosaPackages so gosa-si-server knows how to process this kind of messages
1470                         if ($header =~ /^gosa_/ || $header =~ /^job_/) {
1471                                 $module = "GosaPackages";
1472                         }
1474                         my $res = $incoming_db->add_dbentry( {table=>$incoming_tn,
1475                                         primkey=>[],
1476                                         headertag=>$header,
1477                                         targettag=>$target,
1478                                         xmlmessage=>&encode_base64($msg),
1479                                         timestamp=>&get_time,
1480                                         module=>$module,
1481                                         sessionid=>$session_id,
1482                                 } );
1483                         $kernel->yield('watch_for_next_tasks');
1484                 }
1486                 # target is own address with forward_to_gosa-tag pointing at myself -> forward to gosa
1487                 if (not $done) {
1488                         my $forward_to_gosa =  @{$msg_hash->{'forward_to_gosa'}}[0];
1489                         my $gosa_at;
1490                         my $gosa_session_id;
1491                         if (($target eq $local_address) && (defined $forward_to_gosa)){
1492                                 my ($gosa_at, $gosa_session_id) = split(/,/, $forward_to_gosa);
1493                                 if ($gosa_at eq $local_address) {
1494                                         my $session_reference = $kernel->ID_id_to_session($gosa_session_id);
1495                                         if( defined $session_reference ) {
1496                                                 $heap = $session_reference->get_heap();
1497                                         }
1498                                         if(exists $heap->{'client'}) {
1499                                                 $msg = &encrypt_msg($msg, $GosaPackages_key);
1500                                                 $heap->{'client'}->put($msg);
1501                                                 &daemon_log("$session_id DEBUG: incoming '$header' message forwarded to GOsa", 11); 
1502                                         }
1503                                         $done = 1;
1504                                         &daemon_log("$session_id DEBUG: target is own address with forward_to_gosa-tag pointing at myself -> forward '$header' to gosa", 11);
1505                                 }
1506                         }
1508                 }
1510                 # target is a client address in known_clients -> forward to client
1511                 if (not $done) {
1512                         $sql = "SELECT * FROM $known_clients_tn WHERE (hostname='$target' OR macaddress LIKE '$target')"; 
1513                         $res = $known_clients_db->select_dbentry($sql);
1514                         if (keys(%$res) > 0) 
1515                         {
1516                                 $done = 1; 
1517                                 &daemon_log("$session_id DEBUG: target is a client address in known_clients -> forward '$header' to client", 11);
1518                                 my $hostkey = $res->{1}->{'hostkey'};
1519                                 my $hostname = $res->{1}->{'hostname'};
1520                                 $msg =~ s/<target>\S*<\/target>/<target>$hostname<\/target>/;
1521                                 $msg =~ s/<header>gosa_/<header>/;
1522                                 my $error= &send_msg_to_target($msg, $hostname, $hostkey, $header, $session_id);
1523                                 if ($error) {
1524                                         &daemon_log("$session_id ERROR: Some problems occurred while trying to send msg to client '$hostname': $msg", 1);
1525                                 }
1526                         } 
1527                         else 
1528                         {
1529                                 $not_found_in_known_clients_db = 1;
1530                         }
1531                 }
1533                 # target is a client address in foreign_clients -> forward to registration server
1534                 if (not $done) {
1535                         $sql = "SELECT * FROM $foreign_clients_tn WHERE (hostname='$target' OR macaddress LIKE '$target')";
1536                         $res = $foreign_clients_db->select_dbentry($sql);
1537                         if (keys(%$res) > 0) {
1538                                 my $hostname = $res->{1}->{'hostname'};
1539                                 my ($host_ip, $host_port) = split(/:/, $hostname);
1540                                 my $local_address =  &get_local_ip_for_remote_ip($host_ip).":$server_port";
1541                                 my $regserver = $res->{1}->{'regserver'};
1542                                 my $sql = "SELECT * FROM $known_server_tn WHERE hostname='$regserver'"; 
1543                                 my $res = $known_server_db->select_dbentry($sql);
1544                                 if (keys(%$res) > 0) {
1545                                         my $regserver_key = $res->{1}->{'hostkey'};
1546                                         $msg =~ s/<source>GOSA<\/source>/<source>$local_address<\/source>/;
1547                                         $msg =~ s/<target>$target<\/target>/<target>$hostname<\/target>/;
1548                                         if ($source eq "GOSA") {
1549                                                 $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1550                                         }
1551                                         my $error= &send_msg_to_target($msg, $regserver, $regserver_key, $header, $session_id);
1552                                         if ($error) {
1553                                                 &daemon_log("$session_id ERROR: some problems occurred while trying to send msg to registration server: $msg", 1); 
1554                                         }
1555                                 }
1556                                 $done = 1;
1557                                 &daemon_log("$session_id DEBUG: target is a client address in foreign_clients -> forward '$header' to registration server", 11);
1558                         } else {
1559                                 $not_found_in_foreign_clients_db = 1;
1560                         }
1561                 }
1563                 # target is a server address -> forward to server
1564                 if (not $done) {
1565                         $sql = "SELECT * FROM $known_server_tn WHERE (hostname='$target' OR macaddress LIKE '$target')";
1566                         $res = $known_server_db->select_dbentry($sql);
1567                         if (keys(%$res) > 0) {
1568                                 my $hostkey = $res->{1}->{'hostkey'};
1570                                 if ($source eq "GOSA") {
1571                                         $msg =~ s/<source>GOSA<\/source>/<source>$local_address<\/source>/;
1572                                         $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1574                                 }
1576                                 &send_msg_to_target($msg, $target, $hostkey, $header, $session_id);
1577                                 $done = 1;
1578                                 &daemon_log("$session_id DEBUG: target is a server address -> forward '$header' to server", 11);
1579                         } else {
1580                                 $not_found_in_known_server_db = 1;
1581                         }
1582                 }
1585                 # target is not in foreign_clients_db, known_server_db or known_clients_db, maybe it is a complete new one -> process here
1586                 if ( $not_found_in_foreign_clients_db 
1587                         && $not_found_in_known_server_db
1588                         && $not_found_in_known_clients_db) {
1589                         &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 '$header' here", 11);
1590             if ($header =~ /^gosa_/ || $header =~ /^job_/) { 
1591                 $module = "GosaPackages"; 
1592             }
1593                         my $res = $incoming_db->add_dbentry( {table=>$incoming_tn,
1594                                         primkey=>[],
1595                                         headertag=>$header,
1596                                         targettag=>$target,
1597                                         xmlmessage=>&encode_base64($msg),
1598                                         timestamp=>&get_time,
1599                                         module=>$module,
1600                                         sessionid=>$session_id,
1601                                 } );
1602                         $done = 1;
1603                         $kernel->yield('watch_for_next_tasks');
1604                 }
1607                 if (not $done) {
1608                         daemon_log("$session_id ERROR: do not know what to do with this message: $msg", 1);
1609                         if ($source eq "GOSA") {
1610                                 my %data = ('error_msg' => &encode_base64($msg), 'error_string' => "Do not know what to do with this message!");
1611                                 my $error_msg = &build_msg("error", $local_address, "GOSA", \%data ); 
1613                                 my $session_reference = $kernel->ID_id_to_session($session_id);
1614                                 if( defined $session_reference ) {
1615                                         $heap = $session_reference->get_heap();
1616                                 }
1617                                 if(exists $heap->{'client'}) {
1618                                         $error_msg = &encrypt_msg($error_msg, $GosaPackages_key);
1619                                         $heap->{'client'}->put($error_msg);
1620                                 }
1621                         }
1622                 }
1624         }
1626         return;
1630 sub next_task {
1631     my ($session, $heap, $task) = @_[SESSION, HEAP, ARG0, ARG1];
1632     my $running_task = POE::Wheel::Run->new(
1633             Program => sub { process_task($session, $heap, $task) },
1634             StdioFilter => POE::Filter::Reference->new(),
1635             StdoutEvent  => "task_result",
1636             StderrEvent  => "task_debug",
1637             CloseEvent   => "task_done",
1638             );
1639     $heap->{task}->{ $running_task->ID } = $running_task;
1642 sub handle_task_result {
1643     my ($kernel, $heap, $result) = @_[KERNEL, HEAP, ARG0];
1644     my $client_answer = $result->{'answer'};
1645     if( $client_answer =~ s/session_id=(\d+)$// ) {
1646         my $session_id = $1;
1647         if( defined $session_id ) {
1648             my $session_reference = $kernel->ID_id_to_session($session_id);
1649             if( defined $session_reference ) {
1650                 $heap = $session_reference->get_heap();
1651             }
1652         }
1654         if(exists $heap->{'client'}) {
1655             $heap->{'client'}->put($client_answer);
1656         }
1657     }
1658     $kernel->sig(CHLD => "child_reap");
1661 sub handle_task_debug {
1662     my $result = $_[ARG0];
1663     print STDERR "$result\n";
1666 sub handle_task_done {
1667     my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
1668     delete $heap->{task}->{$task_id};
1669         if (exists $heap->{ldap_handle}->{$task_id}) {
1670                 &release_ldap_handle($heap->{ldap_handle}->{$task_id});
1671         }
1674 sub process_task {
1675     no strict "refs";
1676     #CHECK: Not @_[...]?
1677     my ($session, $heap, $task) = @_;
1678     my $error = 0;
1679     my $answer_l;
1680     my ($answer_header, @answer_target_l, $answer_source);
1681     my $client_answer = "";
1683     # prepare all variables needed to process message
1684     #my $msg = $task->{'xmlmessage'};
1685     my $msg = &decode_base64($task->{'xmlmessage'});
1686     my $incoming_id = $task->{'id'};
1687     my $module = $task->{'module'};
1688     my $header =  $task->{'headertag'};
1689     my $session_id = $task->{'sessionid'};
1690                 my $msg_hash;
1691                 eval {
1692         $msg_hash = $xml->XMLin($msg, ForceArray=>1);
1693                 }; 
1694                 daemon_log("ERROR: XML failure '$@'") if ($@);
1695     my $source = @{$msg_hash->{'source'}}[0];
1696     
1697     # set timestamp of incoming client uptodate, so client will not 
1698     # be deleted from known_clients because of expiration
1699     my $cur_time = &get_time();
1700     my $sql = "UPDATE $known_clients_tn SET timestamp='$cur_time' WHERE hostname='$source'"; 
1701     my $res = $known_clients_db->exec_statement($sql);
1703     ######################
1704     # process incoming msg
1705     if( $error == 0) {
1706         daemon_log("$session_id INFO: Incoming msg (session_id=$session_id) with header '".@{$msg_hash->{'header'}}[0]."'", 5); 
1707         daemon_log("$session_id DEBUG: Processing module ".$module, 26);
1708         $answer_l = &{ $module."::process_incoming_msg" }($msg, $msg_hash, $session_id);
1710         if ( 0 < @{$answer_l} ) {
1711             my $answer_str = join("\n", @{$answer_l});
1712                         my @headers; 
1713             while ($answer_str =~ /<header>(\w+)<\/header>/g) {
1714                                 push(@headers, $1);
1715             }
1716                         daemon_log("$session_id DEBUG: got answer message(s) with header: '".join("', '", @headers)."'", 26);
1717         } else {
1718             daemon_log("$session_id DEBUG: $module: got no answer from module!" ,26);
1719         }
1721     }
1722     if( !$answer_l ) { $error++ };
1724     ########
1725     # answer
1726     if( $error == 0 ) {
1728         foreach my $answer ( @{$answer_l} ) {
1729             # check outgoing msg to xml validity
1730             my ($answer, $answer_hash) = &check_outgoing_xml_validity($answer, $session_id);
1731             if( not defined $answer_hash ) { next; }
1732             
1733             $answer_header = @{$answer_hash->{'header'}}[0];
1734             @answer_target_l = @{$answer_hash->{'target'}};
1735             $answer_source = @{$answer_hash->{'source'}}[0];
1737             # deliver msg to all targets 
1738             foreach my $answer_target ( @answer_target_l ) {
1740                 # targets of msg are all gosa-si-clients in known_clients_db
1741                 if( $answer_target eq "*" ) {
1742                     # answer is for all clients
1743                     my $sql_statement= "SELECT * FROM known_clients";
1744                     my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
1745                     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1746                         my $host_name = $hit->{hostname};
1747                         my $host_key = $hit->{hostkey};
1748                         my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1749                         &update_jobdb_status_for_send_msgs($session_id, $answer, $error);
1750                     }
1751                 }
1753                 # targets of msg are all gosa-si-server in known_server_db
1754                 elsif( $answer_target eq "KNOWN_SERVER" ) {
1755                     # answer is for all server in known_server
1756                     my $sql_statement= "SELECT * FROM $known_server_tn";
1757                     my $query_res = $known_server_db->select_dbentry( $sql_statement ); 
1758                     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1759                         my $host_name = $hit->{hostname};
1760                         my $host_key = $hit->{hostkey};
1761                         $answer =~ s/<target>\S+<\/target>/<target>$host_name<\/target>/g;
1762                         my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1763                         &update_jobdb_status_for_send_msgs($session_id, $answer, $error);
1764                     }
1765                 }
1767                 # target of msg is GOsa
1768                                 elsif( $answer_target eq "GOSA" ) {
1769                                         my $session_id = ($1) if $answer =~ /<session_id>(\d+?)<\/session_id>/;
1770                                         my $add_on = "";
1771                     if( defined $session_id ) {
1772                         $add_on = ".session_id=$session_id";
1773                     }
1774                                         my $header = ($1) if $answer =~ /<header>(\S*)<\/header>/;
1775                                         daemon_log("$session_id INFO: send ".$header." message to GOsa", 5);
1776                                         daemon_log("$session_id DEBUG: message:\n$answer", 12);
1777                     # answer is for GOSA and has to returned to connected client
1778                     my $gosa_answer = &encrypt_msg($answer, $GosaPackages_key);
1779                     $client_answer = $gosa_answer.$add_on;
1780                 }
1782                 # target of msg is job queue at this host
1783                 elsif( $answer_target eq "JOBDB") {
1784                     $answer =~ /<header>(\S+)<\/header>/;   
1785                     my $header;
1786                     if( defined $1 ) { $header = $1; }
1787                     my $error = &send_msg_to_target($answer, $server_address, $GosaPackages_key, $header, $session_id);
1788                     &update_jobdb_status_for_send_msgs($session_id, $answer, $error);
1789                 }
1791                 # Target of msg is a mac address
1792                 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 ) {
1793                     daemon_log("$session_id DEBUG: target is mac address '$answer_target', looking for host in known_clients and foreign_clients", 138);
1795                     # Looking for macaddress in known_clients
1796                     my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$answer_target'";
1797                     my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1798                     my $found_ip_flag = 0;
1799                     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1800                         my $host_name = $hit->{hostname};
1801                         my $host_key = $hit->{hostkey};
1802                         $answer =~ s/$answer_target/$host_name/g;
1803                         daemon_log("$session_id INFO: found host '$host_name', associated to '$answer_target'", 5);
1804                         my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1805                         &update_jobdb_status_for_send_msgs($session_id, $answer, $error);
1806                         $found_ip_flag++ ;
1807                     }   
1809                     # Looking for macaddress in foreign_clients
1810                     if ($found_ip_flag == 0) {
1811                         my $sql = "SELECT * FROM $foreign_clients_tn WHERE macaddress LIKE '$answer_target'";
1812                         my $res = $foreign_clients_db->select_dbentry($sql);
1813                         while( my ($hit_num, $hit) = each %{ $res } ) {
1814                             my $host_name = $hit->{hostname};
1815                             my $reg_server = $hit->{regserver};
1816                             daemon_log("$session_id INFO: found host '$host_name' with mac '$answer_target', registered at '$reg_server'", 5);
1817                             
1818                             # Fetch key for reg_server
1819                             my $reg_server_key;
1820                             my $sql = "SELECT * FROM $known_server_tn WHERE hostname='$reg_server'";
1821                             my $res = $known_server_db->select_dbentry($sql);
1822                             if (exists $res->{1}) {
1823                                 $reg_server_key = $res->{1}->{'hostkey'}; 
1824                             } else {
1825                                 daemon_log("$session_id ERROR: cannot find hostkey for '$host_name' in '$known_server_tn'", 1); 
1826                                 daemon_log("$session_id ERROR: unable to forward answer to correct registration server, processing is aborted!", 1); 
1827                                 $reg_server_key = undef;
1828                             }
1830                             # Send answer to server where client is registered
1831                             if (defined $reg_server_key) {
1832                                 $answer =~ s/$answer_target/$host_name/g;
1833                                 my $error = &send_msg_to_target($answer, $reg_server, $reg_server_key, $answer_header, $session_id);
1834                                 &update_jobdb_status_for_send_msgs($session_id, $answer, $error);
1835                                 $found_ip_flag++ ;
1836                             }
1837                         }
1838                     }
1840                     # No mac to ip matching found
1841                     if( $found_ip_flag == 0) {
1842                         daemon_log("$session_id WARNING: no host found in known_clients or foreign_clients with mac address '$answer_target'", 3);
1843                         &reactivate_job_with_delay($session_id, $answer_target, $answer_header, 30);
1844                     }
1846                 # Answer is for one specific host   
1847                 } else {
1848                     # get encrypt_key
1849                     my $encrypt_key = &get_encrypt_key($answer_target);
1850                     if( not defined $encrypt_key ) {
1851                         # unknown target
1852                         daemon_log("$session_id WARNING: unknown target '$answer_target'", 3);
1853                         next;
1854                     }
1855                     my $error = &send_msg_to_target($answer, $answer_target, $encrypt_key, $answer_header,$session_id);
1856                     &update_jobdb_status_for_send_msgs($session_id, $answer, $error);
1857                 }
1858             }
1859         }
1860     }
1862     my $filter = POE::Filter::Reference->new();
1863     my %result = ( 
1864             status => "seems ok to me",
1865             answer => $client_answer,
1866             );
1868     my $output = $filter->put( [ \%result ] );
1869     print @$output;
1874 sub session_start {
1875     my ($kernel) = $_[KERNEL];
1876     $global_kernel = $kernel;
1877     $kernel->yield('register_at_foreign_servers');
1878         $kernel->yield('create_fai_server_db', $fai_server_tn );
1879         $kernel->yield('create_fai_release_db', $fai_release_tn );
1880         $kernel->sig(USR1 => "sig_handler");
1881         $kernel->sig(USR2 => "recreate_packages_db");
1882         $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1883         $kernel->delay_set('watch_for_done_jobs', $job_queue_loop_delay); 
1884     $kernel->delay_set('watch_for_modified_jobs', $modified_jobs_loop_delay); 
1885         $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay);
1886     $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay);
1887         $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay);
1888     $kernel->delay_set('watch_for_old_known_clients', $job_queue_loop_delay);
1890     # Start opsi check
1891     if ($opsi_enabled eq "true") {
1892         $kernel->delay_set('watch_for_opsi_jobs', $job_queue_opsi_delay); 
1893     }
1898 sub watch_for_done_jobs {
1899         my $kernel = $_[KERNEL];
1901         my $sql_statement = "SELECT * FROM ".$job_queue_tn." WHERE ((status='done') AND (modified='0'))";
1902         my $res = $job_db->select_dbentry( $sql_statement );
1904         while( my ($number, $hit) = each %{$res} ) 
1905         {
1906                 # Non periodical jobs can be deleted.
1907                 if ($hit->{periodic} eq "none")
1908                 {
1909                         my $jobdb_id = $hit->{id};
1910                         my $sql_statement = "DELETE FROM $job_queue_tn WHERE id=$jobdb_id"; 
1911                         my $res = $job_db->del_dbentry($sql_statement); 
1912                 }
1914                 # Periodical jobs should not be deleted but reactivated with new timestamp instead.
1915                 else
1916                 {
1917                         my ($p_time, $periodic) = split("_", $hit->{periodic});
1918                         my $reactivated_ts = $hit->{timestamp};
1919                         my $act_ts = int(&get_time());
1920                         while ($act_ts > int($reactivated_ts))   # Redo calculation to avoid multiple jobs in the past
1921                         {
1922                                 $reactivated_ts = &calc_timestamp($reactivated_ts, "plus", $p_time, $periodic);
1923                         }
1924                         my $sql = "UPDATE $job_queue_tn SET status='waiting', timestamp='$reactivated_ts' WHERE id='".$hit->{id}."'"; 
1925                         my $res = $job_db->exec_statement($sql);
1926                         &daemon_log("J INFO: Update periodical job '".$hit->{headertag}."' for client '".$hit->{targettag}."'. New execution time '$reactivated_ts'.", 5);
1927                 }
1928         }
1930         $kernel->delay_set('watch_for_done_jobs',$job_queue_loop_delay);
1934 sub watch_for_opsi_jobs {
1935     my ($kernel) = $_[KERNEL];
1936     # 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 
1937     # opsi install job is to parse the xml message. There is still the correct header.
1938     my $sql_statement = "SELECT * FROM ".$job_queue_tn." WHERE ((xmlmessage LIKE '%opsi_install_client</header>%') AND (status='processing') AND (siserver='localhost'))";
1939         my $res = $job_db->select_dbentry( $sql_statement );
1941     # Ask OPSI for an update of the running jobs
1942     while (my ($id, $hit) = each %$res ) {
1943         # Determine current parameters of the job
1944         my $hostId = $hit->{'plainname'};
1945         my $macaddress = $hit->{'macaddress'};
1946         my $progress = $hit->{'progress'};
1948         my $result= {};
1949         
1950         # For hosts, only return the products that are or get installed
1951         my $callobj;
1952         $callobj = {
1953             method  => 'getProductStates_hash',
1954             params  => [ $hostId ],
1955             id  => 1,
1956         };
1957         
1958         my $hres = $opsi_client->call($opsi_url, $callobj);
1959                 # TODO : move all opsi relevant statments to opsi plugin
1960                 # The following 3 lines must be tested befor they can replace the rubbish above and below !!!
1961                 #my ($res, $err) = &opsi_com::_getProductStates_hash(hostId=>$hostId)
1962                 #if (not $err) {
1963                 #       my $htmp = $res->{$hostId};
1964                 # 
1965         if (not &check_opsi_res($hres)) {
1966             my $htmp= $hres->result->{$hostId};
1967             # Check state != not_installed or action == setup -> load and add
1968             my $products= 0;
1969             my $installed= 0;
1970             my $installing = 0;
1971             my $error= 0;  
1972             my @installed_list;
1973             my @error_list;
1974             my $act_status = "none";
1975             foreach my $product (@{$htmp}){
1977                 if ($product->{'installationStatus'} ne "not_installed" or
1978                         $product->{'actionRequest'} eq "setup"){
1980                     # Increase number of products for this host
1981                     $products++;
1982         
1983                     if ($product->{'installationStatus'} eq "failed"){
1984                         $result->{$product->{'productId'}}= "error";
1985                         unshift(@error_list, $product->{'productId'});
1986                         $error++;
1987                     }
1988                     if ($product->{'installationStatus'} eq "installed" && $product->{'actionRequest'}  eq "none"){
1989                         $result->{$product->{'productId'}}= "installed";
1990                         unshift(@installed_list, $product->{'productId'});
1991                         $installed++;
1992                     }
1993                     if ($product->{'installationStatus'} eq "installing"){
1994                         $result->{$product->{'productId'}}= "installing";
1995                         $installing++;
1996                         $act_status = "installing - ".$product->{'productId'};
1997                     }
1998                 }
1999             }
2000         
2001             # Estimate "rough" progress, avoid division by zero
2002             if ($products == 0) {
2003                 $result->{'progress'}= 0;
2004             } else {
2005                 $result->{'progress'}= int($installed * 100 / $products);
2006             }
2008             # Set updates in job queue
2009             if ((not $error) && (not $installing) && ($installed)) {
2010                 $act_status = "installed - ".join(", ", @installed_list);
2011             }
2012             if ($error) {
2013                 $act_status = "error - ".join(", ", @error_list);
2014             }
2015             if ($progress ne $result->{'progress'} ) {
2016                 # Updating progress and result 
2017                 my $update_statement = "UPDATE $job_queue_tn SET modified='1', progress='".$result->{'progress'}."', result='$act_status' WHERE macaddress LIKE '$macaddress' AND siserver='localhost'";
2018                 my $update_res = $job_db->update_dbentry($update_statement);
2019             }
2020             if ($progress eq 100) { 
2021                 # Updateing status
2022                 my $done_statement = "UPDATE $job_queue_tn SET modified='1', ";
2023                 if ($error) {
2024                     $done_statement .= "status='error'";
2025                 } else {
2026                     $done_statement .= "status='done'";
2027                 }
2028                 $done_statement .= " WHERE macaddress LIKE '$macaddress' AND siserver='localhost'";
2029                 my $done_res = $job_db->update_dbentry($done_statement);
2030             }
2033         }
2034     }
2036     $kernel->delay_set('watch_for_opsi_jobs', $job_queue_opsi_delay);
2040 # If a job got an update or was modified anyway, send to all other si-server an update message of this jobs.
2041 sub watch_for_modified_jobs {
2042     my ($kernel,$heap) = @_[KERNEL, HEAP];
2044     my $sql_statement = "SELECT * FROM $job_queue_tn WHERE (modified='1')"; 
2045     my $res = $job_db->select_dbentry( $sql_statement );
2046     
2047     # if db contains no jobs which should be update, do nothing
2048     if (keys %$res != 0) {
2050         if ($job_synchronization  eq "true") {
2051             # make out of the db result a gosa-si message   
2052             my $update_msg = &db_res2si_msg ($res, "foreign_job_updates", "KNOWN_SERVER", "MY_LOCAL_ADDRESS");
2053  
2054             # update all other SI-server
2055             &inform_all_other_si_server($update_msg);
2056         }
2058         # set jobs all jobs to modified = 0, wait until the next modification for updates of other si-server
2059         $sql_statement = "UPDATE $job_queue_tn SET modified='0' ";
2060         $res = $job_db->update_dbentry($sql_statement);
2061     }
2063     $kernel->delay_set('watch_for_modified_jobs', $modified_jobs_loop_delay);
2067 sub watch_for_new_jobs {
2068         if($watch_for_new_jobs_in_progress == 0) {
2069                 $watch_for_new_jobs_in_progress = 1;
2070                 my ($kernel,$heap) = @_[KERNEL, HEAP];
2072                 # check gosa job queue for jobs with executable timestamp
2073                 my $timestamp = &get_time();
2074                 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE siserver='localhost' AND status='waiting' AND (CAST(timestamp AS UNSIGNED)) < $timestamp ORDER BY timestamp";
2075                 my $res = $job_db->exec_statement( $sql_statement );
2077                 # Merge all new jobs that would do the same actions
2078                 my @drops;
2079                 my $hits;
2080                 foreach my $hit (reverse @{$res} ) {
2081                         my $macaddress= lc @{$hit}[8];
2082                         my $headertag= @{$hit}[5];
2083                         if(
2084                                 defined($hits->{$macaddress}) &&
2085                                 defined($hits->{$macaddress}->{$headertag}) &&
2086                                 defined($hits->{$macaddress}->{$headertag}[0])
2087                         ) {
2088                                 push @drops, "DELETE FROM $job_queue_tn WHERE id = $hits->{$macaddress}->{$headertag}[0]";
2089                         }
2090                         $hits->{$macaddress}->{$headertag}= $hit;
2091                 }
2093                 # Delete new jobs with a matching job in state 'processing'
2094                 foreach my $macaddress (keys %{$hits}) {
2095                         foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
2096                                 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
2097                                 if(defined($jobdb_id)) {
2098                                         my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND headertag='$jobdb_headertag' AND status='processing'";
2099                                         my $res = $job_db->exec_statement( $sql_statement );
2100                                         foreach my $hit (@{$res}) {
2101                                                 push @drops, "DELETE FROM $job_queue_tn WHERE id=$jobdb_id";
2102                                         }
2103                                 } else {
2104                                         daemon_log("J ERROR: Job without id exists for macaddress $macaddress!", 1);
2105                                 }
2106                         }
2107                 }
2109                 # Commit deletion
2110                 $job_db->exec_statementlist(\@drops);
2112                 # Look for new jobs that could be executed
2113                 foreach my $macaddress (keys %{$hits}) {
2115                         # Look if there is an executing job
2116                         my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND status='processing'";
2117                         my $res = $job_db->exec_statement( $sql_statement );
2119                         # Skip new jobs for host if there is a processing job
2120                         if(defined($res) and defined @{$res}[0]) {
2121                                 # Prevent race condition if there is a trigger_activate job waiting and a goto-activation job processing
2122                                 my $row = @{$res}[0] if (ref $res eq 'ARRAY');
2123                                 if(@{$row}[5] eq 'trigger_action_reinstall') {
2124                                         my $sql_statement_2 =  "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND status='waiting' AND headertag = 'trigger_activate_new'"; 
2125                                         my $res_2 = $job_db->exec_statement( $sql_statement_2 );
2126                                         if(defined($res_2) and defined @{$res_2}[0]) {
2127                                                 # Set status from goto-activation to 'waiting' and update timestamp
2128                                                 $job_db->exec_statement("UPDATE $job_queue_tn SET status='waiting', timestamp='".&calc_timestamp(&get_time(), 'plus', 30)."' WHERE macaddress LIKE '$macaddress' AND headertag = 'trigger_action_reinstall'");
2129                                         }
2130                                 }
2131                                 next;
2132                         }
2134                         foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
2135                                 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
2136                                 if(defined($jobdb_id)) {
2137                                         my $job_msg = @{$hits->{$macaddress}->{$jobdb_headertag}}[7];
2139                                         daemon_log("J DEBUG: its time to execute $job_msg", 7);
2140                                         my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$macaddress'";
2141                                         my $res_hash = $known_clients_db->select_dbentry( $sql_statement );
2143                                         # expect macaddress is unique!!!!!!
2144                                         my $target = $res_hash->{1}->{hostname};
2146                                         # change header
2147                                         $job_msg =~ s/<header>job_/<header>gosa_/;
2149                                         # add sqlite_id
2150                                         $job_msg =~ s/<\/xml>$/<jobdb_id>$jobdb_id<\/jobdb_id><\/xml>/;
2152                                         $job_msg =~ /<header>(\S+)<\/header>/;
2153                                         my $header = $1 ;
2154                                         my $func_error = &send_msg_to_target($job_msg, $server_address, $GosaPackages_key, $header, "J");                    
2156                                         # update status in job queue to ...
2157                     # ... 'processing', for jobs: 'reinstall', 'update', activate_new
2158                     if (($header =~ /gosa_trigger_action_reinstall/) 
2159                             || ($header =~ /gosa_trigger_activate_new/)
2160                             || ($header =~ /gosa_trigger_action_update/)) {
2161                         my $sql_statement = "UPDATE $job_queue_tn SET status='processing' WHERE id=$jobdb_id";
2162                         my $dbres = $job_db->update_dbentry($sql_statement);
2163                     }
2165                     # ... 'done', for all other jobs, they are no longer needed in the jobqueue
2166                     else {
2167                         my $sql_statement = "UPDATE $job_queue_tn SET status='done' WHERE id=$jobdb_id";
2168                         my $dbres = $job_db->exec_statement($sql_statement);
2169                     }
2170                 
2172                                         # We don't want parallel processing
2173                                         last;
2174                                 }
2175                         }
2176                 }
2178                 $watch_for_new_jobs_in_progress = 0;
2179                 $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
2180         }
2184 sub watch_for_new_messages {
2185     my ($kernel,$heap) = @_[KERNEL, HEAP];
2186     my @coll_user_msg;   # collection list of outgoing messages
2187     
2188     # check messaging_db for new incoming messages with executable timestamp
2189     my $timestamp = &get_time();
2190     my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( (CAST(timestamp AS UNSIGNED))<$timestamp AND flag='n' AND direction='in' )";
2191     my $res = $messaging_db->exec_statement( $sql_statement );
2192         foreach my $hit (@{$res}) {
2194         # create outgoing messages
2195         my $message_to = @{$hit}[3];
2196         # translate message_to to plain login name
2197         my @message_to_l = split(/,/, $message_to);  
2198                 my %receiver_h; 
2199                 foreach my $receiver (@message_to_l) {
2200                         if ($receiver =~ /^u_([\s\S]*)$/) {
2201                                 $receiver_h{$1} = 0;
2202                         } elsif ($receiver =~ /^g_([\s\S]*)$/) {
2203                                 my $group_name = $1;
2204                                 # fetch all group members from ldap and add them to receiver hash
2205                                 my $ldap_handle = &get_ldap_handle();
2206                                 if (defined $ldap_handle) {
2207                                                 my $mesg = $ldap_handle->search(
2208                                                                                 base => $ldap_base,
2209                                                                                 scope => 'sub',
2210                                                                                 attrs => ['memberUid'],
2211                                                                                 filter => "cn=$group_name",
2212                                                                                 );
2213                                                 if ($mesg->count) {
2214                                                                 my @entries = $mesg->entries;
2215                                                                 foreach my $entry (@entries) {
2216                                                                                 my @receivers= $entry->get_value("memberUid");
2217                                                                                 foreach my $receiver (@receivers) { 
2218                                                                                                 $receiver_h{$receiver} = 0;
2219                                                                                 }
2220                                                                 }
2221                                                 } 
2222                                                 # translating errors ?
2223                                                 if ($mesg->code) {
2224                                                                 daemon_log("M ERROR: unable to translate group '$group_name' to user list for message delivery: $mesg->error", 1);
2225                                                 }
2226                                                 &release_ldap_handle($ldap_handle);
2227                                 # ldap handle error ?           
2228                                 } else {
2229                                         daemon_log("M ERROR: unable to translate group '$group_name' to user list for message delivery: no ldap handle available", 1);
2230                                 }
2231                         } else {
2232                                 my $sbjct = &encode_base64(@{$hit}[1]);
2233                                 my $msg = &encode_base64(@{$hit}[7]);
2234                                 &daemon_log("M WARNING: unknown receiver '$receiver' for a user-message '$sbjct - $msg'", 3); 
2235                         }
2236                 }
2237                 my @receiver_l = keys(%receiver_h);
2239         my $message_id = @{$hit}[0];
2241         #add each outgoing msg to messaging_db
2242         my $receiver;
2243         foreach $receiver (@receiver_l) {
2244             my $sql_statement = "INSERT INTO $messaging_tn (id, subject, message_from, message_to, flag, direction, delivery_time, message, timestamp) ".
2245                 "VALUES ('".
2246                 $message_id."', '".    # id
2247                 @{$hit}[1]."', '".     # subject
2248                 @{$hit}[2]."', '".     # message_from
2249                 $receiver."', '".      # message_to
2250                 "none"."', '".         # flag
2251                 "out"."', '".          # direction
2252                 @{$hit}[6]."', '".     # delivery_time
2253                 @{$hit}[7]."', '".     # message
2254                 $timestamp."'".     # timestamp
2255                 ")";
2256             &daemon_log("M DEBUG: $sql_statement", 1);
2257             my $res = $messaging_db->exec_statement($sql_statement);
2258             &daemon_log("M INFO: message '".@{$hit}[0]."' is prepared for delivery to receiver '$receiver'", 5);
2259         }
2261         # set incoming message to flag d=deliverd
2262         $sql_statement = "UPDATE $messaging_tn SET flag='p' WHERE id='$message_id'"; 
2263         &daemon_log("M DEBUG: $sql_statement", 7);
2264         $res = $messaging_db->update_dbentry($sql_statement);
2265         &daemon_log("M INFO: message '$message_id' is set to flag 'p' (processed)", 5);
2266     }
2268     $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay); 
2269     return;
2272 sub watch_for_delivery_messages {
2273     my ($kernel, $heap) = @_[KERNEL, HEAP];
2275     # select outgoing messages
2276     my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( flag='p' AND direction='out' )";
2277     my $res = $messaging_db->exec_statement( $sql_statement );
2278     
2279     # build out msg for each    usr
2280     foreach my $hit (@{$res}) {
2281         my $receiver = @{$hit}[3];
2282         my $msg_id = @{$hit}[0];
2283         my $subject = @{$hit}[1];
2284         my $message = @{$hit}[7];
2286         # resolve usr -> host where usr is logged in
2287         my $sql = "SELECT * FROM $login_users_tn WHERE (user='$receiver')"; 
2288         my $res = $login_users_db->exec_statement($sql);
2290         # receiver is logged in nowhere
2291         if (not ref(@$res[0]) eq "ARRAY") { next; }    
2293         # receiver ist logged in at a client registered at local server
2294                 my $send_succeed = 0;
2295                 foreach my $hit (@$res) {
2296                                 my $receiver_host = @$hit[0];
2297                 my $delivered2host = 0;
2298                                 &daemon_log("M DEBUG: user '$receiver' is logged in at host '$receiver_host'", 7);
2300                                 # Looking for host in know_clients_db 
2301                                 my $sql = "SELECT * FROM $known_clients_tn WHERE (hostname='$receiver_host')";
2302                                 my $res = $known_clients_db->exec_statement($sql);
2304                 # Host is known in known_clients_db
2305                 if (ref(@$res[0]) eq "ARRAY") {
2306                     my $receiver_key = @{@{$res}[0]}[2];
2307                     my %data = ('subject' => $subject, 'message' => $message, 'usr' => $receiver);
2308                     my $out_msg = &build_msg("usr_msg", $server_address, $receiver_host, \%data ); 
2309                     my $error = &send_msg_to_target($out_msg, $receiver_host, $receiver_key, "usr_msg", 0); 
2310                     if ($error == 0 ) {
2311                         $send_succeed++ ;
2312                         $delivered2host++ ;
2313                         &daemon_log("M DEBUG: send message for user '$receiver' to host '$receiver_host'", 7); 
2314                     } else {
2315                         &daemon_log("M DEBUG: cannot send message for user '$receiver' to host '$receiver_host'", 7); 
2316                     }
2317                 }
2318                 
2319                 # Message already send, do not need to do anything more, otherwise ...
2320                 if ($delivered2host) { next;}
2321     
2322                 # ...looking for host in foreign_clients_db
2323                 $sql = "SELECT * FROM $foreign_clients_tn WHERE (hostname='$receiver_host')";
2324                 $res = $foreign_clients_db->exec_statement($sql);
2325   
2326                                 # Host is known in foreign_clients_db 
2327                                 if (ref(@$res[0]) eq "ARRAY") { 
2328                     my $registration_server = @{@{$res}[0]}[2];
2329                     
2330                     # Fetch encryption key for registration server
2331                     my $sql = "SELECT * FROM $known_server_tn WHERE (hostname='$registration_server')";
2332                     my $res = $known_server_db->exec_statement($sql);
2333                     if (ref(@$res[0]) eq "ARRAY") { 
2334                         my $registration_server_key = @{@{$res}[0]}[3];
2335                         my %data = ('subject' => $subject, 'message' => $message, 'usr' => $receiver);
2336                         my $out_msg = &build_msg("usr_msg", $server_address, $receiver_host, \%data ); 
2337                         my $error = &send_msg_to_target($out_msg, $registration_server, $registration_server_key, "usr_msg", 0); 
2338                         if ($error == 0 ) {
2339                             $send_succeed++ ;
2340                             $delivered2host++ ;
2341                             &daemon_log("M DEBUG: send message for user '$receiver' to server '$registration_server'", 7); 
2342                         } else {
2343                             &daemon_log("M ERROR: cannot send message for user '$receiver' to server '$registration_server'", 1); 
2344                         }
2346                     } else {
2347                         &daemon_log("M ERROR: host '$receiver_host' is reported to be ".
2348                                 "registrated at server '$registration_server', ".
2349                                 "but no data available in known_server_db ", 1); 
2350                     }
2351                 }
2352                 
2353                 if (not $delivered2host) {
2354                     &daemon_log("M ERROR: unable to send user message to host '$receiver_host'", 1);
2355                 }
2356                 }
2358                 if ($send_succeed) {
2359                                 # set outgoing msg at db to deliverd
2360                                 my $sql = "UPDATE $messaging_tn SET flag='d' WHERE (id='$msg_id' AND direction='out' AND message_to='$receiver')"; 
2361                                 my $res = $messaging_db->exec_statement($sql); 
2362                 &daemon_log("M INFO: send message for user '$receiver' to logged in hosts", 5);
2363                 } else {
2364             &daemon_log("M WARNING: failed to deliver message for user '$receiver'", 3); 
2365         }
2366         }
2368     $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay); 
2369     return;
2373 sub watch_for_done_messages {
2374     my ($kernel,$heap) = @_[KERNEL, HEAP];
2376     my $sql = "SELECT * FROM $messaging_tn WHERE (flag='p' AND direction='in')"; 
2377     my $res = $messaging_db->exec_statement($sql); 
2379     foreach my $hit (@{$res}) {
2380         my $msg_id = @{$hit}[0];
2382         my $sql = "SELECT * FROM $messaging_tn WHERE (id='$msg_id' AND direction='out' AND (NOT flag='s'))"; 
2383         my $res = $messaging_db->exec_statement($sql);
2385         # not all usr msgs have been seen till now
2386         if ( ref(@$res[0]) eq "ARRAY") { next; }
2387         
2388         $sql = "DELETE FROM $messaging_tn WHERE (id='$msg_id')"; 
2389         $res = $messaging_db->exec_statement($sql);
2390     
2391     }
2393     $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay); 
2394     return;
2398 sub watch_for_old_known_clients {
2399     my ($kernel,$heap) = @_[KERNEL, HEAP];
2401     my $sql_statement = "SELECT * FROM $known_clients_tn";
2402     my $res = $known_clients_db->select_dbentry( $sql_statement );
2404     my $cur_time = int(&get_time());
2406     while ( my ($hit_num, $hit) = each %$res) {
2407         my $expired_timestamp = int($hit->{'timestamp'});
2408         $expired_timestamp =~ /(\d{4})(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)/;
2409         my $dt = DateTime->new( year   => $1,
2410                 month  => $2,
2411                 day    => $3,
2412                 hour   => $4,
2413                 minute => $5,
2414                 second => $6,
2415                 );
2417         $dt->add( seconds => 2 * int($hit->{'keylifetime'}) );
2418         $expired_timestamp = $dt->ymd('').$dt->hms('');
2419         if ($cur_time > $expired_timestamp) {
2420             my $hostname = $hit->{'hostname'};
2421             my $del_sql = "DELETE FROM $known_clients_tn WHERE hostname='$hostname'"; 
2422             my $del_res = $known_clients_db->exec_statement($del_sql);
2424             &main::daemon_log("0 INFO: timestamp '".$hit->{'timestamp'}."' of client '$hostname' is expired('$expired_timestamp'), client will be deleted from known_clients_db", 5);
2425         }
2427     }
2429     $kernel->delay_set('watch_for_old_known_clients', $job_queue_loop_delay);
2433 sub watch_for_next_tasks {
2434     my ($kernel,$heap) = @_[KERNEL, HEAP];
2436     my $sql = "SELECT * FROM $incoming_tn";
2437     my $res = $incoming_db->select_dbentry($sql);
2438     
2439     while ( my ($hit_num, $hit) = each %$res) {
2440         my $headertag = $hit->{'headertag'};
2441         if ($headertag =~ /^answer_(\d+)/) {
2442             # do not start processing, this message is for a still running POE::Wheel
2443             next;
2444         }
2445         my $message_id = $hit->{'id'};
2446         my $session_id = $hit->{'sessionid'};
2447         &daemon_log("$session_id DEBUG: start processing for message with incoming id: '$message_id'", 11);
2449         $kernel->yield('next_task', $hit);
2451         my $sql = "DELETE FROM $incoming_tn WHERE id=$message_id";
2452         my $res = $incoming_db->exec_statement($sql);
2453     }
2457 sub get_ldap_handle {
2458         my ($session_id) = @_;
2459         my $heap;
2461         if (not defined $session_id ) { $session_id = 0 };
2462         if ($session_id =~ /[^0-9]*/) { $session_id = 0 };
2464         my ($package, $file, $row, $subroutine, $hasArgs, $wantArray, $evalText, $isRequire) = caller(1);
2465         my $caller_text = "subroutine $subroutine";
2466         if ($subroutine eq "(eval)") {
2467                 $caller_text = "eval block within file '$file' for '$evalText'"; 
2468         }
2469         daemon_log("$session_id DEBUG: new ldap handle for '$caller_text' required!", 42);
2471 get_handle:
2472         my $ldap_handle = Net::LDAP->new( $ldap_uri );
2473         if (not ref $ldap_handle) {
2474                 daemon_log("$session_id ERROR: Connection to LDAP URI '$ldap_uri' failed! Retrying in $ldap_retry_sec seconds.", 1); 
2475                 sleep($ldap_retry_sec);
2476                 goto get_handle;
2477         } else {
2478                 daemon_log("$session_id DEBUG: Connection to LDAP URI '$ldap_uri' established.", 42);
2479         }
2481         $ldap_handle->bind($ldap_admin_dn, password => $ldap_admin_password) or &daemon_log("$session_id ERROR: Could not bind as '$ldap_admin_dn' to LDAP URI '$ldap_uri'!", 1);
2482         return $ldap_handle;
2486 sub release_ldap_handle {
2487         my ($ldap_handle, $session_id) = @_ ;
2488         if (not defined $session_id ) { $session_id = 0 };
2490         if(ref $ldap_handle) {
2491           $ldap_handle->disconnect();
2492   }
2493         &main::daemon_log("$session_id DEBUG: Released a ldap handle!", 42);
2494         return;
2498 sub change_fai_state {
2499         my ($st, $targets, $session_id) = @_;
2500         $session_id = 0 if not defined $session_id;
2501         # Set FAI state to localboot
2502         my %mapActions= (
2503                 reboot    => '',
2504                 update    => 'softupdate',
2505                 localboot => 'localboot',
2506                 reinstall => 'install',
2507                 rescan    => '',
2508                 wake      => '',
2509                 memcheck  => 'memcheck',
2510                 sysinfo   => 'sysinfo',
2511                 install   => 'install',
2512         );
2514         # Return if this is unknown
2515         if (!exists $mapActions{ $st }){
2516                 daemon_log("$session_id ERROR: unknown action '$st', can not translate ot FAIstate", 1); 
2517                 return;
2518         }
2520         my $state= $mapActions{ $st };
2522         # Build search filter for hosts
2523         my $search= "(&(objectClass=GOhard)";
2524         foreach (@{$targets}){
2525                 $search.= "(macAddress=$_)";
2526         }
2527         $search.= ")";
2529         # If there's any host inside of the search string, procress them
2530         if (!($search =~ /macAddress/)){
2531                 daemon_log("$session_id ERROR: no macAddress found in filter statement for LDAP search: '$search'", 1);    
2532                 return;
2533         }
2535         my $ldap_handle = &get_ldap_handle($session_id);
2536         # Perform search for Unit Tag
2537         my $mesg = $ldap_handle->search(
2538                 base   => $ldap_base,
2539                 scope  => 'sub',
2540                 attrs  => ['dn', 'FAIstate', 'objectClass'],
2541                 filter => "$search"
2542         );
2544         if ($mesg->count) {
2545                 my @entries = $mesg->entries;
2546                 if (0 == @entries) {
2547                         daemon_log("$session_id ERROR: ldap search failed: ldap_base=$ldap_base, filter=$search", 1); 
2548                 }
2550                 foreach my $entry (@entries) {
2551                         # Only modify entry if it is not set to '$state'
2552                         if ($entry->get_value("FAIstate") ne "$state"){
2553                                 daemon_log("$session_id INFO: Setting FAIstate to '$state' for ".$entry->dn, 5);
2554                                 my $result;
2555                                 my %tmp = map { $_ => 1 } $entry->get_value("objectClass");
2556                                 if (exists $tmp{'FAIobject'}){
2557                                         if ($state eq ''){
2558                                                 $result= $ldap_handle->modify($entry->dn, changes => [ delete => [ FAIstate => [] ] ]);
2559                                         } else {
2560                                                 $result= $ldap_handle->modify($entry->dn, changes => [ replace => [ FAIstate => $state ] ]);
2561                                         }
2562                                 } elsif ($state ne ''){
2563                                         $result= $ldap_handle->modify($entry->dn, changes => [ add => [ objectClass => 'FAIobject' ], add => [ FAIstate => $state ] ]);
2564                                 }
2566                                 # Errors?
2567                                 if ($result->code){
2568                                         daemon_log("$session_id Error: Setting FAIstate to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
2569                                 }
2570                         } else {
2571                                 daemon_log("$session_id DEBUG FAIstate at host '".$entry->dn."' already at state '$st'", 42); 
2572                         }  
2573                 }
2574         } else {
2575                 daemon_log("$session_id ERROR: LDAP search failed: ldap_base=$ldap_base, filter=$search", 1);
2576         }
2577         &release_ldap_handle($ldap_handle, $session_id);                  
2579         return;
2583 sub change_goto_state {
2584     my ($st, $targets, $session_id) = @_;
2585     $session_id = 0  if not defined $session_id;
2587     # Switch on or off?
2588     my $state= $st eq 'active' ? 'active': 'locked';
2590     my $ldap_handle = &get_ldap_handle($session_id);
2591     if( defined($ldap_handle) ) {
2593       # Build search filter for hosts
2594       my $search= "(&(objectClass=GOhard)";
2595       foreach (@{$targets}){
2596         $search.= "(macAddress=$_)";
2597       }
2598       $search.= ")";
2600       # If there's any host inside of the search string, procress them
2601       if (!($search =~ /macAddress/)){
2602               &release_ldap_handle($ldap_handle);
2603         return;
2604       }
2606       # Perform search for Unit Tag
2607       my $mesg = $ldap_handle->search(
2608           base   => $ldap_base,
2609           scope  => 'sub',
2610           attrs  => ['dn', 'gotoMode'],
2611           filter => "$search"
2612           );
2614       if ($mesg->count) {
2615         my @entries = $mesg->entries;
2616         foreach my $entry (@entries) {
2618           # Only modify entry if it is not set to '$state'
2619           if ($entry->get_value("gotoMode") ne $state){
2621             daemon_log("$session_id INFO: Setting gotoMode to '$state' for ".$entry->dn, 5);
2622             my $result;
2623             $result= $ldap_handle->modify($entry->dn, changes => [replace => [ gotoMode => $state ] ]);
2625             # Errors?
2626             if ($result->code){
2627               &daemon_log("$session_id Error: Setting gotoMode to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
2628             }
2630           }
2631         }
2632       } else {
2633                 daemon_log("$session_id ERROR: LDAP search failed in function change_goto_state: ldap_base=$ldap_base, filter=$search", 1);
2634           }
2636     }
2637         &release_ldap_handle($ldap_handle, $session_id);
2638         return;
2642 sub run_recreate_packages_db {
2643     my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
2644     my $session_id = $session->ID;
2645         &main::daemon_log("$session_id INFO: Recreating FAI Packages DB ('$fai_release_tn', '$fai_server_tn', '$packages_list_tn')", 5);
2646         $kernel->yield('create_fai_release_db', $fai_release_tn);
2647         $kernel->yield('create_fai_server_db', $fai_server_tn);
2648         return;
2652 sub run_create_fai_server_db {
2653     my ($kernel, $session, $heap, $table_name) = @_[KERNEL, SESSION, HEAP, ARG0];
2654     my $session_id = $session->ID;
2655     my $task = POE::Wheel::Run->new(
2656             Program => sub { &create_fai_server_db($table_name,$kernel, undef, $session_id) },
2657             StdoutEvent  => "session_run_result",
2658             StderrEvent  => "session_run_debug",
2659             CloseEvent   => "session_run_done",
2660             );
2662     $heap->{task}->{ $task->ID } = $task;
2663     return;
2667 sub create_fai_server_db {
2668         my ($table_name, $kernel, $dont_create_packages_list, $session_id) = @_;
2669         my $result;
2671         if (not defined $session_id) { $session_id = 0; }
2672         my $ldap_handle = &get_ldap_handle($session_id);
2673         if(defined($ldap_handle)) {
2674                 daemon_log("$session_id INFO: create_fai_server_db: start", 5);
2675                 my $mesg= $ldap_handle->search(
2676                         base   => $ldap_base,
2677                         scope  => 'sub',
2678                         attrs  => ['FAIrepository', 'gosaUnitTag'],
2679                         filter => "(&(FAIrepository=*)(objectClass=FAIrepositoryServer))",
2680                 );
2681                 if($mesg->{'resultCode'} == 0 &&
2682                         $mesg->count != 0) {
2683                         foreach my $entry (@{$mesg->{entries}}) {
2684                                 if($entry->exists('FAIrepository')) {
2685                                         # Add an entry for each Repository configured for server
2686                                         foreach my $repo(@{$entry->get_value('FAIrepository', asref => 1)}) {
2687                                                 my($tmp_url,$tmp_server,$tmp_release,$tmp_sections) = split(/\|/, $repo);
2688                                                 my $tmp_tag= $entry->get_value('gosaUnitTag') || "";
2689                                                 $result= $fai_server_db->add_dbentry( { 
2690                                                                 table => $table_name,
2691                                                                 primkey => ['server', 'fai_release', 'tag'],
2692                                                                 server => $tmp_url,
2693                                                                 fai_release => $tmp_release,
2694                                                                 sections => $tmp_sections,
2695                                                                 tag => (length($tmp_tag)>0)?$tmp_tag:"",
2696                                                         } );
2697                                         }
2698                                 }
2699                         }
2700                 }
2701                 daemon_log("$session_id INFO: create_fai_server_db: finished", 5);
2702                 &release_ldap_handle($ldap_handle);
2704                 # TODO: Find a way to post the 'create_packages_list_db' event
2705                 if(not defined($dont_create_packages_list)) {
2706                         &create_packages_list_db(undef, $session_id);
2707                 }
2708         }       
2710         return $result;
2714 sub run_create_fai_release_db {
2715         my ($kernel, $session, $heap, $table_name) = @_[KERNEL, SESSION, HEAP, ARG0];
2716         my $session_id = $session->ID;
2717         my $task = POE::Wheel::Run->new(
2718                 Program => sub { &create_fai_release_db($table_name, $session_id) },
2719                 StdoutEvent  => "session_run_result",
2720                 StderrEvent  => "session_run_debug",
2721                 CloseEvent   => "session_run_done",
2722         );
2724         $heap->{task}->{ $task->ID } = $task;
2725         return;
2729 sub create_fai_release_db {
2730         my ($table_name, $session_id) = @_;
2731         my $result;
2733         # used for logging
2734         if (not defined $session_id) { $session_id = 0; }
2736         my $ldap_handle = &get_ldap_handle($session_id);
2737         if(defined($ldap_handle)) {
2738                 daemon_log("$session_id INFO: create_fai_release_db: start",5);
2739                 my $mesg= $ldap_handle->search(
2740                         base   => $ldap_base,
2741                         scope  => 'sub',
2742                         attrs  => [],
2743                         filter => "(&(objectClass=organizationalUnit)(ou=fai))",
2744                 );
2745                 if(($mesg->code == 0) && ($mesg->count != 0))
2746                 {
2747                         daemon_log("$session_id DEBUG: create_fai_release_db: count " . $mesg->count,138);
2749                         # Walk through all possible FAI container ou's
2750                         my @sql_list;
2751                         my $timestamp= &get_time();
2752                         foreach my $ou (@{$mesg->{entries}}) {
2753                                 my $tmp_classes= resolve_fai_classes($ou->dn, $ldap_handle, $session_id);
2754                                 if(defined($tmp_classes) && ref($tmp_classes) eq 'HASH') {
2755                                         my @tmp_array=get_fai_release_entries($tmp_classes);
2756                                         if(@tmp_array) {
2757                                                 foreach my $entry (@tmp_array) {
2758                                                         if(defined($entry) && ref($entry) eq 'HASH') {
2759                                                                 my $sql= 
2760                                                                 "INSERT INTO $table_name "
2761                                                                 ."(timestamp, fai_release, class, type, state) VALUES ("
2762                                                                 .$timestamp.","
2763                                                                 ."'".$entry->{'release'}."',"
2764                                                                 ."'".$entry->{'class'}."',"
2765                                                                 ."'".$entry->{'type'}."',"
2766                                                                 ."'".$entry->{'state'}."')";
2767                                                                 push @sql_list, $sql;
2768                                                         }
2769                                                 }
2770                                         }
2771                                 }
2772                         }
2774                         daemon_log("$session_id DEBUG: create_fai_release_db: Inserting ".scalar @sql_list." entries to DB",138);
2775             &release_ldap_handle($ldap_handle);
2776                         if(@sql_list) {
2777                                 unshift @sql_list, "VACUUM";
2778                                 unshift @sql_list, "DELETE FROM $table_name";
2779                                 $fai_release_db->exec_statementlist(\@sql_list);
2780                         }
2781                         daemon_log("$session_id DEBUG: create_fai_release_db: Done with inserting",138);
2782                 } else {
2783                         daemon_log("$session_id INFO: create_fai_release_db: error: " . $mesg->code, 5);
2784                 }
2785                 daemon_log("$session_id INFO: create_fai_release_db: finished",5);
2786         }
2787         return $result;
2790 sub get_fai_types {
2791         my $tmp_classes = shift || return undef;
2792         my @result;
2794         foreach my $type(keys %{$tmp_classes}) {
2795                 if(defined($tmp_classes->{$type}[0]) && (!($tmp_classes->{$type}[0] =~ /^.*?removed.*?$/))) {
2796                         my $entry = {
2797                                 type => $type,
2798                                 state => $tmp_classes->{$type}[0],
2799                         };
2800                         push @result, $entry;
2801                 }
2802         }
2804         return @result;
2807 sub get_fai_state {
2808         my $result = "";
2809         my $tmp_classes = shift || return $result;
2811         foreach my $type(keys %{$tmp_classes}) {
2812                 if(defined($tmp_classes->{$type}[0])) {
2813                         $result = $tmp_classes->{$type}[0];
2814                         
2815                 # State is equal for all types in class
2816                         last;
2817                 }
2818         }
2820         return $result;
2823 sub resolve_fai_classes {
2824         my ($fai_base, $ldap_handle, $session_id) = @_;
2825         if (not defined $session_id) { $session_id = 0; }
2826         my $result;
2827         my @possible_fai_classes= ("FAIscript", "FAIhook", "FAIpartitionTable", "FAItemplate", "FAIvariable", "FAIprofile", "FAIpackageList");
2828         my $fai_filter= "(|(&(objectClass=FAIclass)(|(objectClass=".join(")(objectClass=", @possible_fai_classes).")))(objectClass=FAIbranch))";
2829         my $fai_classes;
2831         daemon_log("$session_id DEBUG: Searching for FAI entries in base $fai_base", 138);
2832         my $mesg= $ldap_handle->search(
2833                 base   => $fai_base,
2834                 scope  => 'sub',
2835                 attrs  => ['cn','objectClass','FAIstate'],
2836                 filter => $fai_filter,
2837         );
2838         daemon_log("$session_id DEBUG: Found ".$mesg->count()." FAI entries", 138);
2840         if($mesg->{'resultCode'} == 0 &&
2841                 $mesg->count != 0) {
2842                 foreach my $entry (@{$mesg->{entries}}) {
2843                         if($entry->exists('cn')) {
2844                                 my $tmp_dn= $entry->dn();
2845                                 $tmp_dn= substr( $tmp_dn, 0, length($tmp_dn)
2846                                         - length($fai_base) - 1 );
2848                                 # Skip classname and ou dn parts for class
2849                                 my $tmp_release = ($1) if $tmp_dn =~ /^[^,]+,[^,]+,(.*?)$/;
2851                                 # Skip classes without releases
2852                                 if((!defined($tmp_release)) || length($tmp_release)==0) {
2853                                         next;
2854                                 }
2856                                 my $tmp_cn= $entry->get_value('cn');
2857                                 my $tmp_state= $entry->get_value('FAIstate');
2859                                 my $tmp_type;
2860                                 # Get FAI type
2861                                 for my $oclass(@{$entry->get_value('objectClass', asref => 1)}) {
2862                                         if(grep $_ eq $oclass, @possible_fai_classes) {
2863                                                 $tmp_type= $oclass;
2864                                                 last;
2865                                         }
2866                                 }
2868                                 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
2869                                         # A Subrelease
2870                                         my @sub_releases = split(/,/, $tmp_release);
2872                                         # Walk through subreleases and build hash tree
2873                                         my $hash;
2874                                         while(my $tmp_sub_release = pop @sub_releases) {
2875                                                 $hash .= "\{'$tmp_sub_release'\}->";                                            
2876                                         }
2877                                         eval('push @{$fai_classes->'.$hash.'{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";');
2878                                 } else {
2879                                         # A branch, no subrelease
2880                                         push @{$fai_classes->{$tmp_release}->{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";
2881                                 }
2882                         } elsif (!$entry->exists('cn')) {
2883                                 my $tmp_dn= $entry->dn();
2884                                 $tmp_dn= substr( $tmp_dn, 0, length($tmp_dn)
2885                                         - length($fai_base) - 1 );
2886                                 my $tmp_release = ($1) if $tmp_dn =~ /^(.*?)$/;
2888                                 # Skip classes without releases
2889                                 if((!defined($tmp_release)) || length($tmp_release)==0) {
2890                                         next;
2891                                 }
2893                                 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
2894                                         # A Subrelease
2895                                         my @sub_releases= split(/,/, $tmp_release);
2897                                         # Walk through subreleases and build hash tree
2898                                         my $hash;
2899                                         while(my $tmp_sub_release = pop @sub_releases) {
2900                                                 $hash .= "\{'$tmp_sub_release'\}->";                                            
2901                                         }
2902                                         # Remove the last two characters
2903                                         chop($hash);
2904                                         chop($hash);
2906                                         eval('$fai_classes->'.$hash.'= {}');
2907                                 } else {
2908                                         # A branch, no subrelease
2909                                         if(!exists($fai_classes->{$tmp_release})) {
2910                                                 $fai_classes->{$tmp_release} = {};
2911                                         }
2912                                 }
2913                         }
2914                 }
2916                 # The hash is complete, now we can honor the copy-on-write based missing entries
2917                 foreach my $release (keys %$fai_classes) {
2918                         $result->{$release}= deep_copy(apply_fai_inheritance($fai_classes->{$release}));
2919                 }
2920         }
2921         return $result;
2924 sub apply_fai_inheritance {
2925        my $fai_classes = shift || return {};
2926        my $tmp_classes;
2928        # Get the classes from the branch
2929        foreach my $class (keys %{$fai_classes}) {
2930                # Skip subreleases
2931                if($class =~ /^ou=.*$/) {
2932                        next;
2933                } else {
2934                        $tmp_classes->{$class}= deep_copy($fai_classes->{$class});
2935                }
2936        }
2938        # Apply to each subrelease
2939        foreach my $subrelease (keys %{$fai_classes}) {
2940                if($subrelease =~ /ou=/) {
2941                        foreach my $tmp_class (keys %{$tmp_classes}) {
2942                                if(!exists($fai_classes->{$subrelease}->{$tmp_class})) {
2943                                        $fai_classes->{$subrelease}->{$tmp_class} =
2944                                        deep_copy($tmp_classes->{$tmp_class});
2945                                } else {
2946                                        foreach my $type (keys %{$tmp_classes->{$tmp_class}}) {
2947                                                if(!exists($fai_classes->{$subrelease}->{$tmp_class}->{$type})) {
2948                                                        $fai_classes->{$subrelease}->{$tmp_class}->{$type}=
2949                                                        deep_copy($tmp_classes->{$tmp_class}->{$type});
2950                                                }
2951                                        }
2952                                }
2953                        }
2954                }
2955        }
2957        # Find subreleases in deeper levels
2958        foreach my $subrelease (keys %{$fai_classes}) {
2959                if($subrelease =~ /ou=/) {
2960                        foreach my $subsubrelease (keys %{$fai_classes->{$subrelease}}) {
2961                                if($subsubrelease =~ /ou=/) {
2962                                        apply_fai_inheritance($fai_classes->{$subrelease});
2963                                }
2964                        }
2965                }
2966        }
2968        return $fai_classes;
2971 sub get_fai_release_entries {
2972         my $tmp_classes = shift || return;
2973         my $parent = shift || "";
2974         my @result = shift || ();
2976         foreach my $entry (keys %{$tmp_classes}) {
2977                 if(defined($entry)) {
2978                         if($entry =~ /^ou=.*$/) {
2979                                 my $release_name = $entry;
2980                                 $release_name =~ s/ou=//g;
2981                                 if(length($parent)>0) {
2982                                         $release_name = $parent."/".$release_name;
2983                                 }
2984                                 my @bufentries = get_fai_release_entries($tmp_classes->{$entry}, $release_name, @result);
2985                                 foreach my $bufentry(@bufentries) {
2986                                         push @result, $bufentry;
2987                                 }
2988                         } else {
2989                                 my @types = get_fai_types($tmp_classes->{$entry});
2990                                 foreach my $type (@types) {
2991                                         push @result, 
2992                                         {
2993                                                 'class' => $entry,
2994                                                 'type' => $type->{'type'},
2995                                                 'release' => $parent,
2996                                                 'state' => $type->{'state'},
2997                                         };
2998                                 }
2999                         }
3000                 }
3001         }
3003         return @result;
3006 sub deep_copy {
3007         my $this = shift;
3008         if (not ref $this) {
3009                 $this;
3010         } elsif (ref $this eq "ARRAY") {
3011                 [map deep_copy($_), @$this];
3012         } elsif (ref $this eq "HASH") {
3013                 +{map { $_ => deep_copy($this->{$_}) } keys %$this};
3014         } else { die "what type is $_?" }
3018 sub session_run_result {
3019     my ($kernel, $heap, $client_answer) = @_[KERNEL, HEAP, ARG0];    
3020     $kernel->sig(CHLD => "child_reap");
3023 sub session_run_debug {
3024     my $result = $_[ARG0];
3025     print STDERR "$result\n";
3028 sub session_run_done {
3029     my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
3030     delete $heap->{task}->{$task_id};
3031         if (exists $heap->{ldap_handle}->{$task_id}) {
3032                 &release_ldap_handle($heap->{ldap_handle}->{$task_id});
3033         }
3034         delete $heap->{ldap_handle}->{$task_id};
3038 sub create_sources_list {
3039         my $session_id = shift || 0;
3040         my $result="/tmp/gosa_si_tmp_sources_list";
3042         # Remove old file
3043         if(stat($result)) {
3044                 unlink($result);
3045                 &main::daemon_log("$session_id DEBUG: remove an old version of '$result'", 7); 
3046         }
3048         open(my $fh, ">", "$result");
3049         if (not defined $fh) {
3050                 &main::daemon_log("$session_id DEBUG: cannot open '$result' for writing", 7); 
3051                 return undef;
3052         }
3053         if(defined($main::ldap_server_dn) and length($main::ldap_server_dn) > 0) {
3054                 my $ldap_handle = &get_ldap_handle($session_id);
3055                 my $mesg=$ldap_handle->search(
3056                         base    => $main::ldap_server_dn,
3057                         scope   => 'base',
3058                         attrs   => 'FAIrepository',
3059                         filter  => 'objectClass=FAIrepositoryServer'
3060                 );
3061                 if($mesg->count) {
3062                         foreach my $entry(@{$mesg->{'entries'}}) {
3063                                 foreach my $value(@{$entry->get_value('FAIrepository', asref => 1)}) {
3064                                         my ($server, $tag, $release, $sections)= split /\|/, $value;
3065                                         my $line = "deb $server $release";
3066                                         $sections =~ s/,/ /g;
3067                                         $line.= " $sections";
3068                                         print $fh $line."\n";
3069                                 }
3070                         }
3071                 }
3072                 &release_ldap_handle($ldap_handle);
3073         } else {
3074                 if (defined $main::ldap_server_dn){
3075                         &main::daemon_log("$session_id ERROR: something wrong with ldap_server_dn '$main::ldap_server_dn', abort create_sources_list", 1); 
3076                 } else {
3077                         &main::daemon_log("$session_id ERROR: no ldap_server_dn found, abort create_sources_list", 1);
3078                 }
3079         }
3080         close($fh);
3082         return $result;
3086 sub run_create_packages_list_db {
3087     my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
3088         my $session_id = $session->ID;
3089         my $task = POE::Wheel::Run->new(
3090                                         Priority => +20,
3091                                         Program => sub {&create_packages_list_db(undef, $session_id)},
3092                                         StdoutEvent  => "session_run_result",
3093                                         StderrEvent  => "session_run_debug",
3094                                         CloseEvent   => "session_run_done",
3095                                         );
3096         $heap->{task}->{ $task->ID } = $task;
3100 sub create_packages_list_db {
3101         my ($sources_file, $session_id) = @_;
3102         
3103         # it should not be possible to trigger a recreation of packages_list_db
3104         # while packages_list_db is under construction, so set flag packages_list_under_construction
3105         # which is tested befor recreation can be started
3106         if (-r $packages_list_under_construction) {
3107                 daemon_log("$session_id WARNING: packages_list_db is right now under construction, please wait until this process is finished", 3);
3108                 return;
3109         } else {
3110                 daemon_log("$session_id INFO: create_packages_list_db: start", 5); 
3111                 # set packages_list_under_construction to true
3112                 system("touch $packages_list_under_construction");
3113                 @packages_list_statements=();
3114         }
3116         if (not defined $session_id) { $session_id = 0; }
3118         if (not defined $sources_file) { 
3119                 &main::daemon_log("$session_id INFO: no sources_file given for creating packages list so trigger creation of it", 5); 
3120                 $sources_file = &create_sources_list($session_id);
3121         }
3123         if (not defined $sources_file) {
3124                 &main::daemon_log("$session_id ERROR: no sources_file given under '$sources_file', skip create_packages_list_db", 1); 
3125                 unlink($packages_list_under_construction);
3126                 return;
3127         }
3129         my $line;
3131         open(my $CONFIG, "<", "$sources_file") or do {
3132                 daemon_log( "$session_id ERROR: create_packages_list_db: Failed to open '$sources_file'", 1);
3133                 unlink($packages_list_under_construction);
3134                 return;
3135         };
3137         # Read lines
3138         while ($line = <$CONFIG>){
3139                 # Unify
3140                 chop($line);
3141                 $line =~ s/^\s+//;
3142                 $line =~ s/^\s+/ /;
3144                 # Strip comments
3145                 $line =~ s/#.*$//g;
3147                 # Skip empty lines
3148                 if ($line =~ /^\s*$/){
3149                         next;
3150                 }
3152                 # Interpret deb line
3153                 if ($line =~ /^deb [^\s]+\s[^\s]+\s[^\s]+/){
3154                         my( $baseurl, $dist, $sections ) = ($line =~ /^deb\s([^\s]+)\s+([^\s]+)\s+(.*)$/);
3155                         my $section;
3156                         foreach $section (split(' ', $sections)){
3157                                 &parse_package_info( $baseurl, $dist, $section, $session_id );
3158                         }
3159                 }
3160     else {
3161         daemon_log("$session_id ERROR: cannot parse line '$line'", 1);
3162     }
3163         }
3165         close ($CONFIG);
3167         if(keys(%repo_dirs)) {
3168                 find(\&cleanup_and_extract, keys( %repo_dirs ));
3169                 &main::strip_packages_list_statements();
3170                 $packages_list_db->exec_statementlist(\@packages_list_statements);
3171         }
3172         unlink($packages_list_under_construction);
3173         daemon_log("$session_id INFO: create_packages_list_db: finished", 5); 
3174         return;
3177 # This function should do some intensive task to minimize the db-traffic
3178 sub strip_packages_list_statements {
3179         my @existing_entries= @{$packages_list_db->exec_statement("SELECT * FROM $main::packages_list_tn")};
3180         my @new_statement_list=();
3181         my $hash;
3182         my $insert_hash;
3183         my $update_hash;
3184         my $delete_hash;
3185         my $known_packages_hash;
3186         my $local_timestamp=get_time();
3188         foreach my $existing_entry (@existing_entries) {
3189                 $hash->{@{$existing_entry}[0]}->{@{$existing_entry}[1]}->{@{$existing_entry}[2]}= $existing_entry;
3190         }
3192         foreach my $statement (@packages_list_statements) {
3193                 if($statement =~ /^INSERT/i) {
3194                         # Assign the values from the insert statement
3195                         my ($distribution,$package,$version,$section,$description,$template,$timestamp) = ($1,$2,$3,$4,$5,$6,$7) if $statement =~ 
3196                         /^INSERT\s+?INTO\s+?$main::packages_list_tn\s+?VALUES\s*?\('(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)'\s*?\)$/si;
3197                         if(exists($hash->{$distribution}->{$package}->{$version})) {
3198                                 # If section or description has changed, update the DB
3199                                 if( 
3200                                         (! (@{$hash->{$distribution}->{$package}->{$version}}[3] eq $section)) or 
3201                                         (! (@{$hash->{$distribution}->{$package}->{$version}}[4] eq $description))
3202                                 ) {
3203                                         @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,undef);
3204                                 } else {
3205                                         # package is already present in database. cache this knowledge for later use
3206                                         @{$known_packages_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
3207                                 }
3208                         } else {
3209                                 # Insert a non-existing entry to db
3210                                 @{$insert_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
3211                         }
3212                 } elsif ($statement =~ /^UPDATE/i) {
3213                         my ($template,$package,$version) = ($1,$2,$3) if $statement =~
3214                         /^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;
3215                         foreach my $distribution (keys %{$hash}) {
3216                                 if(exists($insert_hash->{$distribution}->{$package}->{$version})) {
3217                                         # update the insertion hash to execute only one query per package (insert instead insert+update)
3218                                         @{$insert_hash->{$distribution}->{$package}->{$version}}[5]= $template;
3219                                 } elsif(exists($hash->{$distribution}->{$package}->{$version})) {
3220                                         if( ! (@{$hash->{$distribution}->{$package}->{$version}}[5] eq $template)) {
3221                                                 my $section;
3222                                                 my $description;
3223                                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) and
3224                                                         length(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) > 0 ) {
3225                                                         $section= @{$update_hash->{$distribution}->{$package}->{$version}}[3];
3226                                                 }
3227                                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
3228                                                         $description= @{$update_hash->{$distribution}->{$package}->{$version}}[4];
3229                                                 }
3230                                                 @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
3231                                         }
3232                                 }
3233                         }
3234                 }
3235         }
3237         # Check for orphaned entries
3238         foreach my $existing_entry (@existing_entries) {
3239                 my $distribution= @{$existing_entry}[0];
3240                 my $package= @{$existing_entry}[1];
3241                 my $version= @{$existing_entry}[2];
3242                 my $section= @{$existing_entry}[3];
3244                 if(
3245                         exists($insert_hash->{$distribution}->{$package}->{$version}) ||
3246                         exists($update_hash->{$distribution}->{$package}->{$version}) ||
3247                         exists($known_packages_hash->{$distribution}->{$package}->{$version})
3248                 ) {
3249                         next;
3250                 } else {
3251                         # Insert entry to delete hash
3252                         @{$delete_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section);
3253                 }
3254         }
3256         # unroll the insert hash
3257         foreach my $distribution (keys %{$insert_hash}) {
3258                 foreach my $package (keys %{$insert_hash->{$distribution}}) {
3259                         foreach my $version (keys %{$insert_hash->{$distribution}->{$package}}) {
3260                                 push @new_statement_list, "INSERT INTO $main::packages_list_tn VALUES ('$distribution','$package','$version',"
3261                                 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[3]',"
3262                                 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[4]',"
3263                                 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[5]',"
3264                                 ."'$local_timestamp')";
3265                         }
3266                 }
3267         }
3269         # unroll the update hash
3270         foreach my $distribution (keys %{$update_hash}) {
3271                 foreach my $package (keys %{$update_hash->{$distribution}}) {
3272                         foreach my $version (keys %{$update_hash->{$distribution}->{$package}}) {
3273                                 my $set = "";
3274                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3])) {
3275                                         $set .= "section = '@{$update_hash->{$distribution}->{$package}->{$version}}[3]', ";
3276                                 }
3277                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
3278                                         $set .= "description = '@{$update_hash->{$distribution}->{$package}->{$version}}[4]', ";
3279                                 }
3280                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[5])) {
3281                                         $set .= "template = '@{$update_hash->{$distribution}->{$package}->{$version}}[5]', ";
3282                                 }
3283                                 if(defined($set) and length($set) > 0) {
3284                                         $set .= "timestamp = '$local_timestamp'";
3285                                 } else {
3286                                         next;
3287                                 }
3288                                 push @new_statement_list, 
3289                                 "UPDATE $main::packages_list_tn SET $set WHERE"
3290                                 ." distribution = '$distribution'"
3291                                 ." AND package = '$package'"
3292                                 ." AND version = '$version'";
3293                         }
3294                 }
3295         }
3296         
3297         # unroll the delete hash
3298         foreach my $distribution (keys %{$delete_hash}) {
3299                 foreach my $package (keys %{$delete_hash->{$distribution}}) {
3300                         foreach my $version (keys %{$delete_hash->{$distribution}->{$package}}) {
3301                                 my $section = @{$delete_hash->{$distribution}->{$package}->{$version}}[3];
3302                                 push @new_statement_list, "DELETE FROM $main::packages_list_tn WHERE distribution='$distribution' AND package='$package' AND version='$version' AND section='$section'";
3303                         }
3304                 }
3305         }
3307         unshift(@new_statement_list, "VACUUM");
3309         @packages_list_statements = @new_statement_list;
3313 sub parse_package_info {
3314     my ($baseurl, $dist, $section, $session_id)= @_;
3315     my ($package);
3316     if (not defined $session_id) { $session_id = 0; }
3317     my ($path) = ($baseurl =~ m%://[^/]*(.*)$%);
3318     $repo_dirs{ "${repo_path}/pool" } = 1;
3320     foreach $package ("Packages.gz"){
3321         daemon_log("$session_id DEBUG: create_packages_list: fetch $baseurl, $dist, $section", 266);
3322         get_package( "$baseurl/dists/$dist/$section/binary-$arch/$package", "$outdir/$dist/$section", $session_id );
3323         parse_package( "$outdir/$dist/$section", $dist, $path, $session_id );
3324     }
3325     
3329 sub get_package {
3330     my ($url, $dest, $session_id)= @_;
3331     if (not defined $session_id) { $session_id = 0; }
3333     my $tpath = dirname($dest);
3334     -d "$tpath" || mkpath "$tpath";
3336     # This is ugly, but I've no time to take a look at "how it works in perl"
3337     if(0 == system("wget '$url' -O '$dest' 2>/dev/null") ) {
3338         system("gunzip -cd '$dest' > '$dest.in'");
3339         daemon_log("$session_id DEBUG: run command: gunzip -cd '$dest' > '$dest.in'", 266);
3340         unlink($dest);
3341         daemon_log("$session_id DEBUG: delete file '$dest'", 266); 
3342     } else {
3343         daemon_log("$session_id ERROR: create_packages_list_db: get_packages: fetching '$url' into '$dest' failed!", 1);
3344     }
3345     return 0;
3349 sub parse_package {
3350     my ($path, $dist, $srv_path, $session_id)= @_;
3351     if (not defined $session_id) { $session_id = 0;}
3352     my ($package, $version, $section, $description);
3353     my $timestamp = &get_time();
3355     if(not stat("$path.in")) {
3356         daemon_log("$session_id ERROR: create_packages_list: parse_package: file '$path.in' is not readable",1);
3357         return;
3358     }
3360     open(my $PACKAGES, "<", "$path.in");
3361     if(not defined($PACKAGES)) {
3362         daemon_log("$session_id ERROR: create_packages_list_db: parse_package: cannot open '$path.in'",1); 
3363         return;
3364     }
3366     # Read lines
3367     while (<$PACKAGES>){
3368         my $line = $_;
3369         # Unify
3370         chop($line);
3372         # Use empty lines as a trigger
3373         if ($line =~ /^\s*$/){
3374             my $sql = "INSERT INTO packages_list VALUES ('$dist', '$package', '$version', '$section', '$description', '', '$timestamp')";
3375             push(@packages_list_statements, $sql);
3376             $package = "none";
3377             $version = "none";
3378             $section = "none";
3379             $description = "none"; 
3380             next;
3381         }
3383         # Trigger for package name
3384         if ($line =~ /^Package:\s/){
3385             ($package)= ($line =~ /^Package: (.*)$/);
3386             next;
3387         }
3389         # Trigger for version
3390         if ($line =~ /^Version:\s/){
3391             ($version)= ($line =~ /^Version: (.*)$/);
3392             next;
3393         }
3395         # Trigger for description
3396         if ($line =~ /^Description:\s/){
3397             ($description)= &encode_base64(($line =~ /^Description: (.*)$/));
3398             next;
3399         }
3401         # Trigger for section
3402         if ($line =~ /^Section:\s/){
3403             ($section)= ($line =~ /^Section: (.*)$/);
3404             next;
3405         }
3407         # Trigger for filename
3408         if ($line =~ /^Filename:\s/){
3409             my ($filename) = ($line =~ /^Filename: (.*)$/);
3410             store_fileinfo( $package, $filename, $dist, $srv_path, $version, $repo_path );
3411             next;
3412         }
3413     }
3415     close( $PACKAGES );
3416     unlink( "$path.in" );
3420 sub store_fileinfo {
3421     my( $package, $file, $dist, $path, $vers, $srvdir) = @_;
3423     my %fileinfo = (
3424         'package' => $package,
3425         'dist' => $dist,
3426         'version' => $vers,
3427     );
3429     $repo_files{ "${srvdir}/$file" } = \%fileinfo;
3433 sub cleanup_and_extract {
3434         my $fileinfo = $repo_files{ $File::Find::name };
3436         if( defined $fileinfo ) {
3437                 my $dir = "$outdir/$fileinfo->{ 'dist' }/debconf.d";
3438                 my $sql;
3439                 my $package = $fileinfo->{ 'package' };
3440                 my $newver = $fileinfo->{ 'version' };
3442                 mkpath($dir);
3443                 system( "dpkg -e '$File::Find::name' '$dir/DEBIAN'" );
3445                 if( -f "$dir/DEBIAN/templates" ) {
3447                         daemon_log("0 DEBUG: Found debconf templates in '$package' - $newver", 266);
3449                         my $tmpl= ""; {
3450                                 local $/=undef;
3451                                 open(my $FILE, "$dir/DEBIAN/templates");
3452                                 $tmpl = &encode_base64(<$FILE>);
3453                                 close($FILE);
3454                         }
3455                         rmtree("$dir/DEBIAN/templates");
3457                         $sql= "update $main::packages_list_tn set template = '$tmpl' where package = '$package' and version = '$newver';";
3458                         push @packages_list_statements, $sql;
3459                 }
3460         }
3462         return;
3466 sub prepare_server_registration 
3468         # Add foreign server from cfg file
3469         my @foreign_server_list;
3470         if ($foreign_server_string ne "") {
3471             my @cfg_foreign_server_list = split(",", $foreign_server_string);
3472             foreach my $foreign_server (@cfg_foreign_server_list) {
3473                 push(@foreign_server_list, $foreign_server);
3474             }
3475         
3476             daemon_log("0 INFO: found foreign server in config file: ".join(", ", @foreign_server_list), 5);
3477         }
3478         
3479         # Perform a DNS lookup for server registration if flag is true
3480         if ($dns_lookup eq "true") {
3481             # Add foreign server from dns
3482             my @tmp_servers;
3483             if (not $server_domain) {
3484                 # Try our DNS Searchlist
3485                 for my $domain(get_dns_domains()) {
3486                     chomp($domain);
3487                     my ($tmp_domains, $error_string) = &get_server_addresses($domain);
3488                     if(@$tmp_domains) {
3489                         for my $tmp_server(@$tmp_domains) {
3490                             push @tmp_servers, $tmp_server;
3491                         }
3492                     }
3493                 }
3494                 if(@tmp_servers && length(@tmp_servers)==0) {
3495                     daemon_log("0 WARNING: no foreign gosa-si-server found in DNS for domain '$server_domain'", 3);
3496                 }
3497             } else {
3498                 @tmp_servers = &get_server_addresses($server_domain);
3499                 if( 0 == @tmp_servers ) {
3500                     daemon_log("0 WARNING: no foreign gosa-si-server found in DNS for domain '$server_domain'", 3);
3501                 }
3502             }
3503         
3504             daemon_log("0 INFO: found foreign server via DNS ".join(", ", @tmp_servers), 5);    
3505         
3506             foreach my $server (@tmp_servers) { 
3507                 unshift(@foreign_server_list, $server); 
3508             }
3509         } else {
3510             daemon_log("0 INFO: DNS lookup for server registration is disabled", 5);
3511         }
3512         
3513         # eliminate duplicate entries
3514         @foreign_server_list = &del_doubles(@foreign_server_list);
3515         my $all_foreign_server = join(", ", @foreign_server_list);
3516         daemon_log("0 INFO: found foreign server in config file and DNS: '$all_foreign_server'", 5);
3517         
3518         # add all found foreign servers to known_server
3519         my $cur_timestamp = &get_time();
3520         foreach my $foreign_server (@foreign_server_list) {
3521         
3522                 # do not add myself to known_server_db
3523                 if (&is_local($foreign_server)) { next; }
3524                 ######################################
3525         
3526             my $res = $known_server_db->add_dbentry( {table=>$known_server_tn, 
3527                     primkey=>['hostname'],
3528                     hostname=>$foreign_server,
3529                     macaddress=>"",
3530                     status=>'not_yet_registered',
3531                     hostkey=>"none",
3532                     loaded_modules => "none", 
3533                     timestamp=>$cur_timestamp,
3534                                 update_time=>'19700101000000',
3535                     } );
3536         }
3539 sub register_at_foreign_servers {   
3540     my ($kernel) = $_[KERNEL];
3542         # Update status and update-time of all si-server with expired update_time and 
3543         # block them for race conditional registration processes of other si-servers.
3544         my $act_time = &get_time();
3545         my $block_statement = "UPDATE $known_server_tn SET status='new_server',update_time='19700101000000' WHERE (CAST(update_time AS UNSIGNED))<$act_time ";
3546         my $block_res = $known_server_db->exec_statement($block_statement);
3548         # Fetch all si-server from db where update_time is younger than act_time
3549         my $fetch_statement = "SELECT * FROM $known_server_tn WHERE update_time='19700101000000'"; 
3550         my $fetch_res = $known_server_db->exec_statement($fetch_statement);
3552     # Detect already connected clients. Will be added to registration msg later. 
3553     my $client_sql = "SELECT * FROM $known_clients_tn"; 
3554     my $client_res = $known_clients_db->exec_statement($client_sql);
3556         # Send registration messag to all fetched si-server
3557     foreach my $hit (@$fetch_res) {
3558         my $hostname = @$hit[0];
3559         my $hostkey = &create_passwd;
3561         # Add already connected clients to registration message 
3562         my $myhash = &create_xml_hash('new_server', $server_address, $hostname);
3563         &add_content2xml_hash($myhash, 'key', $hostkey);
3564         map(&add_content2xml_hash($myhash, 'client', @{$_}[0].",".@{$_}[4]), @$client_res);
3566         # Add locally loaded gosa-si modules to registration message
3567         my $loaded_modules = {};
3568         while (my ($package, $pck_info) = each %$known_modules) {
3569                         next if ((!defined(@$pck_info[2])) || (!(ref (@$pck_info[2]) eq 'HASH')));
3570                         foreach my $act_module (keys(%{@$pck_info[2]})) {
3571                                 $loaded_modules->{$act_module} = ""; 
3572                         }
3573                 }
3574         map(&add_content2xml_hash($myhash, "loaded_modules", $_), keys(%$loaded_modules));
3576         # Add macaddress to registration message
3577         my ($host_ip, $host_port) = split(/:/, $hostname);
3578         my $local_ip = &get_local_ip_for_remote_ip($host_ip);
3579         my $network_interface= &get_interface_for_ip($local_ip);
3580         my $host_mac = &get_mac_for_interface($network_interface);
3581         &add_content2xml_hash($myhash, 'macaddress', $host_mac);
3582         
3583         # Build registration message and send it
3584         my $foreign_server_msg = &create_xml_string($myhash);
3585         my $error = &send_msg_to_target($foreign_server_msg, $hostname, $ServerPackages_key, "new_server", 0); 
3586     }
3589         # After n sec perform a check of all server registration processes
3590     $kernel->delay_set("control_server_registration", 2); 
3592         return;
3596 sub control_server_registration {
3597         my ($kernel) = $_[KERNEL];
3598         
3599         # Check if all registration processes succeed or not
3600         my $select_statement = "SELECT * FROM $known_server_tn WHERE status='new_server'"; 
3601         my $select_res = $known_server_db->exec_statement($select_statement);
3603         # If at least one registration process failed, maybe in case of a race condition
3604         # with a foreign registration process
3605         if (@$select_res > 0) 
3606         {
3607                 # Release block statement 'new_server' to make the server accessible
3608                 # for foreign registration processes
3609                 my $update_statement = "UPDATE $known_server_tn SET status='waiting' WHERE status='new_server'";        
3610                 my $update_res = $known_server_db->exec_statement($update_statement);
3612                 # Set a random delay to avoid the registration race condition
3613                 my $new_foreign_servers_register_delay = int(rand(4))+1;
3614                 $kernel->delay_set("register_at_foreign_servers", $new_foreign_servers_register_delay);
3615         }
3616         # If all registration processes succeed
3617         else
3618         {
3619                 $kernel->delay_set("register_at_foreign_servers", $foreign_servers_register_delay);
3620         }
3622         return;
3625 sub start_daemon {
3627         if( ! $foreground ) { 
3628                 chdir '/'                 or die "Can't chdir to /: $!";
3629                 umask 0;
3630                 open STDIN, '+>/dev/null'   or die "Can't read /dev/null: $!";
3631                 open STDOUT, '+>&STDIN' or die "Can't write to /dev/null: $!";
3632                 open STDERR, '+>&STDIN' or die "Can't write to /dev/null: $!";
3633                 defined(my $child_pid = fork)   or die "Can't fork: $!";
3634                 exit if $child_pid;
3635                 setsid                    or die "Can't start a new session: $!";
3636         }
3637         return;
3640 sub put_version {
3642         # parse head url and revision from svn
3643         $server_version =~ /^\$HeadURL: (\S+) \$:\$Rev: (\d+) \$$/;
3644         $server_headURL = defined $1 ? $1 : 'unknown' ;
3645         $server_revision = defined $2 ? $2 : 'unknown' ;
3646         if ($server_headURL =~ /\/tag\// ||
3647         $server_headURL =~ /\/branches\// ) {
3648     $server_status = "stable";
3649         } else {
3650     $server_status = "developmental" ;
3651         }
3652         return;
3655 sub get_perms_admin {
3656 # Determine root uid and adm gid, used for creating log files
3657         $root_uid = getpwnam('root');
3658         $adm_gid = getgrnam('adm');
3659         if(not defined $adm_gid){
3660                 $adm_gid = getgrnam('root');
3661         }
3662         return;
3665 sub open_log {
3666         # Prepare log file and set permissions
3667         open(my $log, ">>", "$log_file");
3668         close($log);
3669         chmod(0440, $log_file);
3670         chown($root_uid, $adm_gid, $log_file);
3672         return;
3675 sub create_pid {
3676         # Create the PID object
3677         $pid = File::Pid->new({ file  => $pid_file });
3679         # Write the PID file
3680         $pid->write;
3682         return;
3686 #==== MAIN = main ==============================================================
3688 # Parse options and allow '-vvv'
3689 Getopt::Long::Configure( 'bundling' );
3690 GetOptions( 'v|verbose+' => \$verbose,
3691             'h|help' => \&usage,
3692             'c|config=s' => \$config,
3693             'x|dump-config=i' => \$dump_config,
3694             'f|foreground' => \$foreground,
3695                                                 'd=s' => \$debug_parts)
3696   or usage( '', 1 );
3698 # We may want to dump the default configuration
3699 if( defined $dump_config ) {
3700   if($dump_config==1) {
3701         } elsif ($dump_config==2) {
3702     dump_configuration( $dump_config ); 
3703         } else {
3704     usage( "Dump configuration value has to be 1 or 2" );
3705         }
3708 #  read and set config parameters
3709 &read_configfile($config, %cfg_defaults);
3711 # create pid file
3712 &create_pid($pid, $pid_file);
3714 # daemonize the program
3715 &start_daemon($foreground);
3717 # Determine root uid and adm gid, used for creating log files
3718 &get_perms_admin($root_uid, $adm_gid);
3720 # put version
3721 &put_version($server_status_hash, $server_version, $server_headURL, $server_revision, $server_status);
3723 #open log file
3724 &open_log($root_uid, $adm_gid, $log_file);
3726 # prepare directory for databases
3727 mkpath('/var/lib/gosa-si', 0, {owner=>'root', group=>'root'});
3729 # remove leftover files in tmp for packaged.db populate
3730 rmtree( '/tmp/packages_list_db',0,1);
3732 # remove list of sources from apt.sources.list
3733 unlink '/tmp/gosa_si_tmp_sources_list';
3735 # remove marker that the list creation is in progress
3736 unlink '/tmp/packages_list_creation_in_progress';
3738 daemon_log(" ", 1);
3739 daemon_log("$0 started!", 1);
3740 daemon_log("status: $server_status", 1);
3741 daemon_log($server_status_hash->{$server_status}.": $server_revision", 1); 
3743 # Buildup data bases
3745     no strict "refs";
3747                 daemon_log("0 INFO: importing database module '$db_module'", 1);
3749     if ($db_module eq "DBmysql") {
3750     
3751         # connect to incoming_db
3752         $incoming_db = ("GOsaSI::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3754         # connect to gosa-si job queue
3755         $job_db = ("GOsaSI::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3757         # connect to known_clients_db
3758         $known_clients_db = ("GOsaSI::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3760         # connect to foreign_clients_db
3761         $foreign_clients_db = ("GOsaSI::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3763         # connect to known_server_db
3764         $known_server_db = ("GOsaSI::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3766         # connect to login_usr_db
3767         $login_users_db = ("GOsaSI::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3769         # connect to fai_server_db 
3770         $fai_server_db = ("GOsaSI::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3772         # connect to fai_release_db
3773         $fai_release_db = ("GOsaSI::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3775         # connect to packages_list_db
3776         $packages_list_db = ("GOsaSI::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3778         # connect to messaging_db
3779         $messaging_db = ("GOsaSI::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3781     } elsif ($db_module eq "DBsqlite") {
3783         daemon_log("0 INFO: Removing SQLlite lock files", 1);
3785                                 # delete old DBsqlite lock files to be replace by rmtree !!
3786                                 system('rm -f /var/lib/gosa-si/*.si.lock*');
3787                                 #rmtree( '/var/lib/gosa-si/',0,1);
3789         # connect to incoming_db
3790         unlink($incoming_file_name);
3791         $incoming_db = ("GOsaSI::".$db_module)->new($incoming_file_name);
3792                                 #chmod(0640, $incoming_file_name);
3793                                 #chown($root_uid, $adm_gid, $incoming_file_name);
3794         
3795         # connect to gosa-si job queue
3796         $job_db = ("GOsaSI::".$db_module)->new($job_queue_file_name);
3797                                 #chmod(0640, $job_queue_file_name);
3798                                 #chown($root_uid, $adm_gid, $job_queue_file_name);
3799         
3800         # connect to known_clients_db
3801         #unlink($known_clients_file_name);
3802         $known_clients_db = ("GOsaSI::".$db_module)->new($known_clients_file_name);
3803                                 #chmod(0640, $known_clients_file_name);
3804                                 #chown($root_uid, $adm_gid, $known_clients_file_name);
3805         
3806         # connect to foreign_clients_db
3807         #unlink($foreign_clients_file_name);
3808         $foreign_clients_db = ("GOsaSI::".$db_module)->new($foreign_clients_file_name);
3809                                 #chmod(0640, $foreign_clients_file_name);
3810                                 #chown($root_uid, $adm_gid, $foreign_clients_file_name);
3811         
3812         # connect to known_server_db
3813                                 #unlink($known_server_file_name);   # do not delete, gosa-si-server should be forced to check config file and dns at each start
3814         $known_server_db = ("GOsaSI::".$db_module)->new($known_server_file_name);
3815                                 #chmod(0640, $known_server_file_name);
3816                                 #chown($root_uid, $adm_gid, $known_server_file_name);
3817         
3818         # connect to login_usr_db
3819         #unlink($login_users_file_name);
3820         $login_users_db = ("GOsaSI::".$db_module)->new($login_users_file_name);
3821                                 #chmod(0640, $login_users_file_name);
3822                                 #chown($root_uid, $adm_gid, $login_users_file_name);
3823         
3824         # connect to fai_server_db
3825                                 #unlink($fai_server_file_name);
3826         $fai_server_db = ("GOsaSI::".$db_module)->new($fai_server_file_name);
3827                                 #chmod(0640, $fai_server_file_name);
3828                                 #chown($root_uid, $adm_gid, $fai_server_file_name);
3829         
3830         # connect to fai_release_db
3831                                 #unlink($fai_release_file_name);
3832         $fai_release_db = ("GOsaSI::".$db_module)->new($fai_release_file_name);
3833                                 #chmod(0640, $fai_release_file_name);
3834                                 #chown($root_uid, $adm_gid, $fai_release_file_name);
3835         
3836         # connect to packages_list_db
3837                                 #unlink($packages_list_under_construction);
3838         $packages_list_db = ("GOsaSI::".$db_module)->new($packages_list_file_name);
3839                                 #chmod(0640, $packages_list_file_name);
3840                                 #chown($root_uid, $adm_gid, $packages_list_file_name);
3841         
3842         # connect to messaging_db
3843         #unlink($messaging_file_name);
3844         $messaging_db = ("GOsaSI::".$db_module)->new($messaging_file_name);
3845                                 #chmod(0640, $messaging_file_name);
3846                                 #chown($root_uid, $adm_gid, $messaging_file_name);
3847     }
3850 # Creating tables
3852 daemon_log("0 INFO: creating tables in database with '$db_module'", 1);
3854 $messaging_db->create_table($messaging_tn, \@messaging_col_names);
3855 $packages_list_db->create_table($packages_list_tn, \@packages_list_col_names);
3856 $fai_release_db->create_table($fai_release_tn, \@fai_release_col_names);
3857 $fai_server_db->create_table($fai_server_tn, \@fai_server_col_names);
3858 $login_users_db->create_table($login_users_tn, \@login_users_col_names);
3859 $known_server_db->create_table($known_server_tn, \@known_server_col_names);
3860 $foreign_clients_db->create_table($foreign_clients_tn, \@foreign_clients_col_names);
3861 $known_clients_db->create_table($known_clients_tn, \@known_clients_col_names);
3862 $incoming_db->create_table($incoming_tn, \@incoming_col_names);
3863 $job_db->create_table($job_queue_tn, \@job_queue_col_names);
3865 # create xml object used for en/decrypting
3866 $xml = new XML::Simple();
3868 # Import all modules
3869 &import_modules;
3871 # Check wether all modules are gosa-si valid passwd check
3872 &password_check;
3874 # Check DNS and config file for server registration
3875 if ($serverPackages_enabled eq "true") { &prepare_server_registration; }
3877 # Create functions hash
3878 while (my ($module, @mod_info) = each %$known_modules) 
3880         while (my ($plugin, $functions) = each %{$mod_info[0][2]})
3881         {
3882                 while (my ($function, $nothing) = each %$functions )
3883                 {
3884                         $known_functions->{$function} = $nothing;
3885                 }
3886         }
3889 # Prepare for using Opsi 
3890 if ($opsi_enabled eq "true") {
3891     use JSON::RPC::Client;
3892     use XML::Quote qw(:all);
3893     $opsi_url= "https://".$opsi_admin.":".$opsi_password."@".$opsi_server.":4447/rpc";
3894     $opsi_client = new JSON::RPC::Client;
3897 daemon_log("0 INFO: start socket for incoming xml messages at port '$server_port' ", 1);
3899 POE::Component::Server::TCP->new(
3900         Alias => "TCP_SERVER",
3901         Port => $server_port,
3902         ClientInput => sub {
3903                 my ($kernel, $input, $heap, $session) = @_[KERNEL, ARG0, HEAP, SESSION];
3904         my $session_id = $session->ID;
3905                 if ($input =~ /;([\d\.]+):([\d]+)$/) 
3906                 {
3907                         # Messages from other servers should be blocked if config option is set
3908                         if (($2 eq $server_port) && ($serverPackages_enabled eq "false"))
3909                         {
3910                                 return;
3911                         }
3912                         &daemon_log("$session_id DEBUG: incoming message from '$1:$2'", 11);
3913                 }
3914                 else
3915                 {
3916                         my $remote_ip = $heap->{'remote_ip'};
3917                         &daemon_log("$session_id DEBUG: incoming message from '$remote_ip'", 11);
3918                 }
3919                 push(@msgs_to_decrypt, $input);
3920                 $kernel->yield("msg_to_decrypt");
3921         },
3922         InlineStates => {
3923                 msg_to_decrypt => \&msg_to_decrypt,
3924                 watch_for_next_tasks => \&watch_for_next_tasks,
3925                 next_task => \&next_task,
3926                 task_result => \&handle_task_result,
3927                 task_done   => \&handle_task_done,
3928                 task_debug  => \&handle_task_debug,
3929                 child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!"  },
3930         }
3931 );
3933 # create session for repeatedly checking the job queue for jobs
3934 POE::Session->create(
3935         inline_states => {
3936                                 _start => \&session_start,
3937         register_at_foreign_servers => \&register_at_foreign_servers,
3938                                 control_server_registration => \&control_server_registration,
3939         sig_handler => \&sig_handler,
3940         next_task => \&next_task,
3941         task_result => \&handle_task_result,
3942         task_done   => \&handle_task_done,
3943         task_debug  => \&handle_task_debug,
3944         watch_for_new_messages => \&watch_for_new_messages,
3945         watch_for_delivery_messages => \&watch_for_delivery_messages,
3946         watch_for_done_messages => \&watch_for_done_messages,
3947                                 watch_for_new_jobs => \&watch_for_new_jobs,
3948         watch_for_modified_jobs => \&watch_for_modified_jobs,
3949         watch_for_done_jobs => \&watch_for_done_jobs,
3950         watch_for_opsi_jobs => \&watch_for_opsi_jobs,
3951         watch_for_old_known_clients => \&watch_for_old_known_clients,
3952         create_packages_list_db => \&run_create_packages_list_db,
3953         create_fai_server_db => \&run_create_fai_server_db,
3954         create_fai_release_db => \&run_create_fai_release_db,
3955                                 recreate_packages_db => \&run_recreate_packages_db,
3956         session_run_result => \&session_run_result,
3957         session_run_debug => \&session_run_debug,
3958         session_run_done => \&session_run_done,
3959         child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!"  },
3960         }
3961 );
3964 POE::Kernel->run();
3965 exit;