Code

5b21a7ced9acfa36a1af4bc7c0cd8c32385a0a70
[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]
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
59 =head1 DESCRIPTION
61 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.
64 =head1 BUGS 
66 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>
69 =head1 LICENCE AND COPYRIGHT
71 This code is part of GOsa (L<http://www.gosa-project.org>)
73 Copyright (C) 2003-2010 GONICUS GmbH
75 This program is distributed in the hope that it will be useful,
76 but WITHOUT ANY WARRANTY; without even the implied warranty of
77 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
78 GNU General Public License for more details.
80 =cut
82 use strict;
83 use warnings;
85 use Getopt::Long;
86 use Config::IniFiles;
87 use IO::Socket::INET;
88 use IO::Handle;
89 use IO::Select;
90 use Crypt::Rijndael;
91 use MIME::Base64;
92 use Digest::MD5  qw(md5 md5_hex md5_base64);
93 use XML::Simple;
94 use Data::Dumper;
95 use Sys::Syslog qw( :DEFAULT setlogsock);
96 use Time::HiRes qw( usleep clock_gettime );
97 use File::Spec;
98 use File::Basename;
99 use File::Find;
100 use File::Copy;
101 use File::Path;
102 use Net::LDAP;
103 use Net::LDAP::Util qw(:escape);
104 use File::Pid;
105 use GOsaSI::GosaSupportDaemon;
107 use POE qw(Component::Server::TCP Wheel::Run Filter::Reference);
108 use Symbol qw(qualify_to_ref);
109 use Fcntl qw/:flock/;
110 use POSIX;
112 my $server_version = '$HeadURL: https://oss.gonicus.de/repositories/gosa/trunk/gosa-si/gosa-si-server $:$Rev$';
114 # revision number of server and program name
115 my $server_headURL;
116 my $server_revision;
117 my $server_status;
119 my $db_module = "DBsqlite";
121 no strict "refs";
122 require ("GOsaSI/".$db_module.".pm");
123 ("GOsaSI/".$db_module)->import;
126 my $modules_path = "/usr/lib/gosa-si/modules";
127 use lib "/usr/lib/gosa-si/modules";
129 my ($foreground, $ping_timeout);
130 my ($server);
131 my ($gosa_server, $job_queue_timeout, $job_queue_loop_delay);
132 my ($messaging_db_loop_delay);
133 my $procid;
134 my $arp_fifo;
135 my $debug_parts = 0;
136 my $debug_parts_bitstring;
137 my ($xml);
138 my $sources_list;
139 my $max_clients;
140 my %repo_files=();
141 my $repo_path;
142 my %repo_dirs=();
144 # Variables declared in config file are always set to 'our'
145 our (%cfg_defaults, $log_file, $pid_file, $pid
146     $server_ip, $server_port, $ClientPackages_key, $dns_lookup,
147     $arp_activ, $gosa_unit_tag,
148     $GosaPackages_key, $gosa_timeout,
149     $serverPackages_enabled, $foreign_server_string, $server_domain, $ServerPackages_key, $foreign_servers_register_delay,
150     $wake_on_lan_passwd, $job_synchronization, $modified_jobs_loop_delay,
151     $arp_enabled, $arp_interface,
152     $opsi_enabled, $opsi_server, $opsi_admin, $opsi_password,
153     $new_systems_ou,
154     $arch,
155 );
157 # additional variable which should be globaly accessable
158 our $server_address;
159 our $server_mac_address;
160 our $gosa_address;
161 our $no_arp;
162 our $forground;
163 our $cfg_file;
164 our ($ldap_uri, $ldap_base, $ldap_admin_dn, $ldap_admin_password, $ldap_server_dn, $ldap_version, $ldap_retry_sec);
165 our ($mysql_username, $mysql_password, $mysql_database, $mysql_host);
166 our $known_modules;
167 our $known_functions;
168 our $root_uid;
169 our $adm_gid;
170 our $verbose= 0;
171 our $global_kernel;
173 # where is the config stored by default and his name
174 our $config = '/etc/gosa-si/server.conf';
176 # by default dumping of config is undefined
177 our $dump_config = undef;
179 # if foreground is not null, script will be not forked to background
180 $foreground = 0 ;
182 # specifies the timeout seconds while checking the online status of a registrating client
183 $ping_timeout = 5;
185 $no_arp = 0;
186 my $packages_list_under_construction = "/tmp/packages_list_creation_in_progress";
187 my @packages_list_statements;
188 my $watch_for_new_jobs_in_progress = 0;
190 # holds all incoming decrypted messages
191 our $incoming_db;
192 our $incoming_tn = 'incoming';
193 my $incoming_file_name;
194 my @incoming_col_names = ("id INTEGER PRIMARY KEY",
195         "timestamp VARCHAR(14) DEFAULT 'none'", 
196         "headertag VARCHAR(255) DEFAULT 'none'",
197         "targettag VARCHAR(255) DEFAULT 'none'",
198         "xmlmessage TEXT",
199         "module VARCHAR(255) DEFAULT 'none'",
200         "sessionid VARCHAR(255) DEFAULT '0'",
201 );
203 # holds all gosa jobs
204 our $job_db;
205 our $job_queue_tn = 'jobs';
206 my $job_queue_file_name;
207 my @job_queue_col_names = ("id INTEGER PRIMARY KEY",
208         "timestamp VARCHAR(14) DEFAULT 'none'", 
209         "status VARCHAR(255) DEFAULT 'none'", 
210         "result TEXT",
211         "progress VARCHAR(255) DEFAULT 'none'",
212         "headertag VARCHAR(255) DEFAULT 'none'",
213         "targettag VARCHAR(255) DEFAULT 'none'", 
214         "xmlmessage TEXT", 
215         "macaddress VARCHAR(17) DEFAULT 'none'",
216         "plainname VARCHAR(255) DEFAULT 'none'",
217         "siserver VARCHAR(255) DEFAULT 'none'",
218         "modified INTEGER DEFAULT '0'",
219         "periodic VARCHAR(6) DEFAULT 'none'",
220 );
222 # holds all other gosa-si-server
223 our $known_server_db;
224 our $known_server_tn = "known_server";
225 my $known_server_file_name;
226 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)");
228 # holds all registrated clients
229 our $known_clients_db;
230 our $known_clients_tn = "known_clients";
231 my $known_clients_file_name;
232 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)");
234 # holds all registered clients at a foreign server
235 our $foreign_clients_db;
236 our $foreign_clients_tn = "foreign_clients"; 
237 my $foreign_clients_file_name;
238 my @foreign_clients_col_names = ("hostname VARCHAR(255)", "macaddress VARCHAR(17)", "regserver VARCHAR(255)", "timestamp VARCHAR(14)");
240 # holds all logged in user at each client 
241 our $login_users_db;
242 our $login_users_tn = "login_users";
243 my $login_users_file_name;
244 my @login_users_col_names = ("client VARCHAR(255)", "user VARCHAR(255)", "timestamp VARCHAR(14)", "regserver VARCHAR(255) DEFAULT 'localhost'");
246 # holds all fai server, the debian release and tag
247 our $fai_server_db;
248 our $fai_server_tn = "fai_server"; 
249 my $fai_server_file_name;
250 our @fai_server_col_names = ("timestamp VARCHAR(14)", "server VARCHAR(255)", "fai_release VARCHAR(255)", "sections VARCHAR(255)", "tag VARCHAR(255)"); 
252 our $fai_release_db;
253 our $fai_release_tn = "fai_release"; 
254 my $fai_release_file_name;
255 our @fai_release_col_names = ("timestamp VARCHAR(14)", "fai_release VARCHAR(255)", "class VARCHAR(255)", "type VARCHAR(255)", "state VARCHAR(255)"); 
257 # holds all packages available from different repositories
258 our $packages_list_db;
259 our $packages_list_tn = "packages_list";
260 my $packages_list_file_name;
261 our @packages_list_col_names = ("distribution VARCHAR(255)", "package VARCHAR(255)", "version VARCHAR(255)", "section VARCHAR(255)", "description TEXT", "template LONGBLOB", "timestamp VARCHAR(14)");
262 my $outdir = "/tmp/packages_list_db";
264 # holds all messages which should be delivered to a user
265 our $messaging_db;
266 our $messaging_tn = "messaging"; 
267 our @messaging_col_names = ("id INTEGER", "subject TEXT", "message_from VARCHAR(255)", "message_to VARCHAR(255)", 
268         "flag VARCHAR(255)", "direction VARCHAR(255)", "delivery_time VARCHAR(255)", "message TEXT", "timestamp VARCHAR(14)" );
269 my $messaging_file_name;
271 # path to directory to store client install log files
272 our $client_fai_log_dir = "/var/log/fai"; 
274 # queue which stores taskes until one of the $max_children children are ready to process the task
275 #my @tasks = qw();
276 my @msgs_to_decrypt = qw();
277 my $max_children = 2;
280 # loop delay for job queue to look for opsi jobs
281 my $job_queue_opsi_delay = 10;
282 our $opsi_client;
283 our $opsi_url;
284  
285 # Lifetime of logged in user information. If no update information comes after n seconds, 
286 # the user is expeceted to be no longer logged in or the host is no longer running. Because
287 # of this, the user is deleted from login_users_db
288 our $logged_in_user_date_of_expiry = 600;
290 # List of month names, used in function daemon_log
291 my @monthnames = ("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec");
293 # List of accepted periodical xml tags related to cpan modul DateTime
294 our $check_periodic = {"months"=>'', "weeks"=>'', "days"=>'', "hours"=>'', "minutes"=>''};
297 %cfg_defaults = (
298 "General" => {
299     "log-file" => [\$log_file, "/var/log/gosa-si/gosa-si-server.log"],
300     "pid-file" => [\$pid_file, "/var/run/gosa-si/gosa-si-server.pid"],
301     },
302 "Server" => {
303     "ip"                    => [\$server_ip, "0.0.0.0"],
304     "port"                  => [\$server_port, "20081"],
305     "known-clients"         => [\$known_clients_file_name, '/var/lib/gosa-si/clients.db' ],
306     "known-servers"         => [\$known_server_file_name, '/var/lib/gosa-si/servers.db'],
307     "incoming"              => [\$incoming_file_name, '/var/lib/gosa-si/incoming.db'],
308     "login-users"           => [\$login_users_file_name, '/var/lib/gosa-si/users.db'],
309     "fai-server"            => [\$fai_server_file_name, '/var/lib/gosa-si/fai_server.db'],
310     "fai-release"           => [\$fai_release_file_name, '/var/lib/gosa-si/fai_release.db'],
311     "packages-list"         => [\$packages_list_file_name, '/var/lib/gosa-si/packages.db'],
312     "messaging"             => [\$messaging_file_name, '/var/lib/gosa-si/messaging.db'],
313     "foreign-clients"       => [\$foreign_clients_file_name, '/var/lib/gosa-si/foreign_clients.db'],
314     "source-list"           => [\$sources_list, '/etc/apt/sources.list'],
315     "repo-path"             => [\$repo_path, '/srv/www'],
316     "debian-arch"           => [\$arch, 'i386'],     
317     "ldap-uri"              => [\$ldap_uri, ""],
318     "ldap-base"             => [\$ldap_base, ""],
319     "ldap-admin-dn"         => [\$ldap_admin_dn, ""],
320     "ldap-admin-password"   => [\$ldap_admin_password, ""],
321     "ldap-version"          => [\$ldap_version, 3],
322     "ldap-retry-sec"        => [\$ldap_retry_sec, 10],
323     "gosa-unit-tag"         => [\$gosa_unit_tag, ""],
324     "max-clients"           => [\$max_clients, 10],
325     "wol-password"          => [\$wake_on_lan_passwd, ""],
326     "mysql-username"        => [\$mysql_username, "gosa_si"],
327     "mysql-password"        => [\$mysql_password, ""],
328     "mysql-database"        => [\$mysql_database, "gosa_si"],
329     "mysql-host"            => [\$mysql_host, "127.0.0.1"],
330     },
331 "GOsaPackages" => {
332     "job-queue" => [\$job_queue_file_name, '/var/lib/gosa-si/jobs.db'],
333     "job-queue-loop-delay" => [\$job_queue_loop_delay, 3],
334     "messaging-db-loop-delay" => [\$messaging_db_loop_delay, 3],
335     "key" => [\$GosaPackages_key, "none"],
336     "new-systems-ou" => [\$new_systems_ou, 'ou=workstations,ou=systems'],
337     },
338 "ClientPackages" => {
339     "key" => [\$ClientPackages_key, "none"],
340     "user-date-of-expiry" => [\$logged_in_user_date_of_expiry, 600],
341     },
342 "ServerPackages"=> {
343     "enabled"      => [\$serverPackages_enabled, "true"],
344     "address"      => [\$foreign_server_string, ""],
345     "dns-lookup"   => [\$dns_lookup, "true"],
346     "domain"       => [\$server_domain, ""],
347     "key"          => [\$ServerPackages_key, "none"],
348     "key-lifetime" => [\$foreign_servers_register_delay, 120],
349     "job-synchronization-enabled" => [\$job_synchronization, "true"],
350     "synchronization-loop" => [\$modified_jobs_loop_delay, 5],
351     },
352 "ArpHandler" => {
353     "enabled"   => [\$arp_enabled, "false"],
354     "interface" => [\$arp_interface, "all"],
355         },
356 "Opsi" => {
357     "enabled"  => [\$opsi_enabled, "false"], 
358     "server"   => [\$opsi_server, "localhost"],
359     "admin"    => [\$opsi_admin, "opsi-admin"],
360     "password" => [\$opsi_password, "secret"],
361    },
363 );
365 #############################
367 # @brief Display error message and/or help text.
369 # In correspondence to previous GetOptions
371 # @param $text - string to print as error message
372 # @param $help - set true, if you want to show usage help
374 sub usage
376   my( $text, $help ) = @_;
378   $text = undef if( 'h' eq $text );
379   (defined $text) && print STDERR "\n$text\n";
381   if( (defined $help && $help)
382       || (!defined $help && !defined $text) )
384     print STDERR << "EOF";
386   usage: $0 [-hvf] [-c config] [-d number]
388    -h        : this (help) message
389    -c <file> : config file (default: ${config})
390    -x <cfg>  : dump configuration to stdout
391              ( 1 = current, 2 = default )
392    -f        : foreground (don't fork)
393            -v        : be verbose (multiple to increase verbosity)
394                               'v': error logs
395                             'vvv': warning plus error logs                                              
396                           'vvvvv': info plus warning logs
397                         'vvvvvvv': debug plus info logs
398            -no-arp   : starts $prg without connection to arp module
399            -d <int>  : if verbose level is higher than 7x 'v' specified parts can be debugged
400                            1 : report incoming messages
401                            2 : report unencrypted outgoing messages 
402                            4 : report encrypting key for messages
403                            8 : report decrypted incoming message and verification if the message complies gosa-si requirements
404                           16 : message processing
405                           32 : ldap connectivity
406                           64 : database status and connectivity
407                          128 : main process 
408                          256 : creation of packages_list_db
409                          512 : ARP debug information
410 EOF
412   print( "\n" );
414   exit( -1 );
417 #############################
419 # @brief Manage gosa-si-client configuration.
421 # Will exit after successfull dump to stdout (type = 1 | 2)
423 # Dump type can be:
424 #   1: Current gosa-si-client configuration in config file (exit)
425 #   2: Default gosa-si-client configuration (exit)
426 #   3: Dump to logfile (no exit)
428 # @param int config type
430 sub dump_configuration {
432   my( $cfg_type ) = @_;
434   return if( ! defined $cfg_type );
436   if(1==$cfg_type ) {
437     print( "# Current gosa-si-server configuration\n" );
438         } elsif (2==$cfg_type) {
439     print( "# Default gosa-si-server configuration\n" );
440         } elsif (3==$cfg_type) {
441     daemon_log( "Dumping gosa-si-server configuration\n", 2 );
442         } else {
443     return;
444         }
446   foreach my $section (keys %cfg_defaults) {
447     if( 3 != $cfg_type ) { 
448       print( "\n[${section}]\n" ); 
449         } else {
450       daemon_log( "\n  [${section}]\n", 3 ); 
451         }
453                 foreach my $param (sort( keys %{$cfg_defaults{ $section }})) {
454       my $pinfo = $cfg_defaults{ $section }{ $param };
455       my $value;
456       if (1==$cfg_type) {
457         if( defined( ${@$pinfo[ 0 ]} ) ) {
458           $value = ${@$pinfo[ 0 ]};
459           print( "$param=$value\n" );
460                                 } else {
461           print( "#${param}=\n" ); 
462                                 }
463                         } elsif (2==$cfg_type) {
464         $value = @{$pinfo}[ 1 ];
465         if( defined( @$pinfo[ 1 ] ) ) {
466           $value = @{$pinfo}[ 1 ];
467           print( "$param=$value\n" );
468                                 } else {
469           print( "#${param}=\n" ); 
470                                 }
471                         } elsif (3==$cfg_type) {
472         if( defined(  ${@$pinfo[ 0 ]} ) ) {
473           $value = ${@$pinfo[ 0 ]};
474           daemon_log( "  $param=$value\n", 3 )
475                                 }
476                         }
477                 }
478         }
481 # We just exit at stdout dump
482   if( 3 == $cfg_type ) { 
483     daemon_log( "\n", 3 );
484         } else {
485     exit( 0 );
486         }
490 #===  FUNCTION  ================================================================
491 #         NAME:  logging
492 #   PARAMETERS:  level - string - default 'info'
493 #                msg - string -
494 #                facility - string - default 'LOG_DAEMON'
495 #      RETURNS:  nothing
496 #  DESCRIPTION:  function for logging
497 #===============================================================================
498 sub daemon_log {
499     my( $msg, $level ) = @_;
500     if (not defined $msg) { return }
501     if (not defined $level) { $level = 1 }
502         my $to_be_logged = 0;
504         # Write log line if line level is lower than verbosity given in commandline
505         if ($level <= $verbose) 
506         { 
507                 $to_be_logged = 1 ;
508         }
510         # Write if debug flag is set and bitstring matches
511         if ($debug_parts > 0)
512         {
513                 my $tmp_level = ($level - 10 >= 0) ? $level - 10 : 0 ;
514                 my $tmp_level_bitstring = unpack("B32", pack("N", $tmp_level));
515                 if (int($debug_parts_bitstring & $tmp_level_bitstring)) 
516                 {
517                         $to_be_logged = 1;
518                 }
519         }
521         if ($to_be_logged) 
522         {
523                 if(defined $log_file){
524                         my $open_log_fh = sysopen(LOG_HANDLE, $log_file, O_WRONLY | O_CREAT | O_APPEND , 0440);
525                         if(not $open_log_fh) {
526                                 print STDERR "cannot open $log_file: $!";
527                                 return;
528                         }
529                         # Check owner and group of log_file and update settings if necessary
530                         my ($dev, $ino, $mode, $nlink, $uid, $gid, $rdev, $size, $atime, $mtime, $ctime, $blksize, $blocks) = stat($log_file);
531                         if((not $uid eq $root_uid) || (not $gid eq $adm_gid)) {
532                                 chown($root_uid, $adm_gid, $log_file);
533                         }
535                         # Prepare time string for log message
536                         my ($seconds,$minutes,$hours,$monthday,$month,$year,$weekday,$yearday,$sommertime) = localtime(time);
537                         $hours = $hours < 10 ? $hours = "0".$hours : $hours;
538                         $minutes = $minutes < 10 ? $minutes = "0".$minutes : $minutes;
539                         $seconds = $seconds < 10 ? $seconds = "0".$seconds : $seconds;
540                         $month = $monthnames[$month];
541                         $monthday = $monthday < 10 ? $monthday = "0".$monthday : $monthday;
542                         $year+=1900;
544                         # Microseconds since epoch
545                         my $microSeconds = sprintf("%.2f", &Time::HiRes::clock_gettime());
546                         $microSeconds =~ s/^\d*(.\d\d)$/$1/;
548                         # Build log message and write it to log file and commandline
549                         chomp($msg);
550                         my $log_msg = "$month $monthday $hours:$minutes:$seconds$microSeconds $prg $msg\n";
551                         flock(LOG_HANDLE, LOCK_EX);
552                         seek(LOG_HANDLE, 0, 2);
553                         print LOG_HANDLE $log_msg;
554                         flock(LOG_HANDLE, LOCK_UN);
555                         if( $foreground ) 
556                         { 
557                                 print STDERR $log_msg;
558                         }
559                         close( LOG_HANDLE );
560                 }
561         }
565 #===  FUNCTION  ================================================================
566 #         NAME:  check_cmdline_param
567 #   PARAMETERS:  nothing
568 #      RETURNS:  nothing
569 #  DESCRIPTION:  validates commandline parameter
570 #===============================================================================
571 sub check_cmdline_param () {
572     my $err_counter = 0;
574         # Check configuration file
575         if(not defined($cfg_file)) {
576                 $cfg_file = "/etc/gosa-si/server.conf";
577                 if(! -r $cfg_file) {
578                         print STDERR "Please specify a config file.\n";
579                         $err_counter++;
580                 }
581     }
583         # Prepare identification which gosa-si parts should be debugged and which not
584         if (defined $debug_parts) 
585         {
586                 if ($debug_parts =~ /^\d+$/)
587                 {
588                         $debug_parts_bitstring = unpack("B32", pack("N", $debug_parts));
589                 }
590                 else
591                 {
592                         print STDERR "Value '$debug_parts' invalid for option d (number expected)\n";
593                         $err_counter++;
594                 }
595         }
597         # Exit if an error occour
598     if( $err_counter > 0 ) { &usage( "", 1 ); }
602 #===  FUNCTION  ================================================================
603 #         NAME:  check_pid
604 #   PARAMETERS:  nothing
605 #      RETURNS:  nothing
606 #  DESCRIPTION:  handels pid processing
607 #===============================================================================
608 sub check_pid {
609     $pid = -1;
610     # Check, if we are already running
611     if( open( my $LOCK_FILE, "<", "$pid_file" ) ) {
612         $pid = <$LOCK_FILE>;
613         if( defined $pid ) {
614             chomp( $pid );
615             if( -f "/proc/$pid/stat" ) {
616                 my($stat) = `cat /proc/$pid/stat` =~ m/$pid \((.+)\).*/;
617                 if( $stat ) {
618                                         print STDERR "\nERROR: Already running!\n";
619                     close( $LOCK_FILE );
620                     exit -1;
621                 }
622             }
623         }
624         close( $LOCK_FILE );
625         unlink( $pid_file );
626     }
628     # create a syslog msg if it is not to possible to open PID file
629     if (not sysopen(my $LOCK_FILE, $pid_file, O_WRONLY|O_CREAT|O_EXCL, 0644)) {
630         my($msg) = "Couldn't obtain lockfile '$pid_file' ";
631         if (open(my $LOCK_FILE, '<', $pid_file)
632                 && ($pid = <$LOCK_FILE>))
633         {
634             chomp($pid);
635             $msg .= "(PID $pid)\n";
636         } else {
637             $msg .= "(unable to read PID)\n";
638         }
639         if( ! ($foreground) ) {
640             openlog( $0, "cons,pid", "daemon" );
641             syslog( "warning", $msg );
642             closelog();
643         }
644         else {
645             print( STDERR " $msg " );
646         }
647         exit( -1 );
648     }
651 #===  FUNCTION  ================================================================
652 #         NAME:  import_modules
653 #   PARAMETERS:  module_path - string - abs. path to the directory the modules 
654 #                are stored
655 #      RETURNS:  nothing
656 #  DESCRIPTION:  each file in module_path which ends with '.pm' and activation 
657 #                state is on is imported by "require 'file';"
658 #===============================================================================
659 sub import_modules {
660     if (not -e $modules_path) {
661         daemon_log("0 ERROR: cannot find directory or directory is not readable: $modules_path", 1);   
662     }
664     opendir (DIR, $modules_path) or die "ERROR while loading modules from directory $modules_path : $!\n";
666     while (defined (my $file = readdir (DIR))) {
667         if (not $file =~ /(\S*?).pm$/) {
668             next;
669         }
670                 my $mod_name = $1;
672         # ArpHandler switch
673         if( $file =~ /ArpHandler.pm/ ) {
674             if( $arp_enabled eq "false" ) { next; }
675         }
677                 # ServerPackages switch
678                 if ($file eq "ServerPackages.pm" && $serverPackages_enabled eq "false") 
679                 {
680                         $dns_lookup = "false";
681                         next; 
682                 }
683         
684         eval { require $file; };
685         if ($@) {
686             daemon_log("0 ERROR: gosa-si-server could not load module $file", 1);
687             daemon_log("$@", 1);
688             exit;
689                 } else {
690                         my $info = eval($mod_name.'::get_module_info()');
691                         # Only load module if get_module_info() returns a non-null object
692                         if( $info ) {
693                                 my ($input_address, $input_key, $event_hash) = @{$info};
694                                 $known_modules->{$mod_name} = $info;
695                                 daemon_log("0 INFO: module $mod_name loaded", 5);
696                         }
697                 }
698     }   
699     close (DIR);
702 #===  FUNCTION  ================================================================
703 #         NAME:  password_check
704 #   PARAMETERS:  nothing
705 #      RETURNS:  nothing
706 #  DESCRIPTION:  escalates an critical error if two modules exist which are avaialable by 
707 #                the same password
708 #===============================================================================
709 sub password_check {
710     my $passwd_hash = {};
711     while (my ($mod_name, $mod_info) = each %$known_modules) {
712         my $mod_passwd = @$mod_info[1];
713         if (not defined $mod_passwd) { next; }
714         if (not exists $passwd_hash->{$mod_passwd}) {
715             $passwd_hash->{$mod_passwd} = $mod_name;
717         # escalates critical error
718         } else {
719             &daemon_log("0 ERROR: two loaded modules do have the same password. Please modify the 'key'-parameter in config file");
720             &daemon_log("0 ERROR: module='$mod_name' and module='".$passwd_hash->{$mod_passwd}."'");
721             exit( -1 );
722         }
723     }
728 #===  FUNCTION  ================================================================
729 #         NAME:  sig_int_handler
730 #   PARAMETERS:  signal - string - signal came from system
731 #      RETURNS:  nothing
732 #  DESCRIPTION:  handle tasks to be done before signal becomes active
733 #===============================================================================
734 sub sig_int_handler {
735     my ($signal) = @_;
737 #       if (defined($ldap_handle)) {
738 #               $ldap_handle->disconnect;
739 #       }
740     # TODO alle verbliebenden ldap verbindungen aus allen heaps beenden
741     
742     daemon_log("shutting down gosa-si-server", 1);
744                 $global_kernel->yield('shutdown');
746                 # to be removed rather crude !!
747                 #system("kill `ps -C gosa-si-server -o pid=`");
749                 $pid->remove or warn "Could not remove $pidfile\n";
751                 exit(0);
753 $SIG{INT} = \&sig_int_handler;
756 sub check_key_and_xml_validity {
757     my ($crypted_msg, $module_key, $session_id) = @_;
758     my $msg;
759     my $msg_hash;
760     my $error_string;
761     eval{
762         $msg = &decrypt_msg($crypted_msg, $module_key);
764         if ($msg =~ /<xml>/i){
765             $msg =~ s/\s+/ /g;  # just for better daemon_log
766             daemon_log("$session_id DEBUG: decrypted_msg: \n$msg", 18);
767             $msg_hash = $xml->XMLin($msg, ForceArray=>1);
769             ##############
770             # check header
771             if( not exists $msg_hash->{'header'} ) { die "no header specified"; }
772             my $header_l = $msg_hash->{'header'};
773             if( (1 > @{$header_l}) || ( ( 'HASH' eq ref @{$header_l}[0]) && (1 > keys %{@{$header_l}[0]}) ) ) { die 'empty header tag'; }
774             if( 1 < @{$header_l} ) { die 'more than one header specified'; }
775             my $header = @{$header_l}[0];
776             if( 0 == length $header) { die 'empty string in header tag'; }
778             ##############
779             # check source
780             if( not exists $msg_hash->{'source'} ) { die "no source specified"; }
781             my $source_l = $msg_hash->{'source'};
782             if( (1 > @{$source_l}) || ( ( 'HASH' eq ref @{$source_l}[0]) && (1 > keys %{@{$source_l}[0]}) ) ) { die 'empty source tag'; }
783             if( 1 < @{$source_l} ) { die 'more than one source specified'; }
784             my $source = @{$source_l}[0];
785             if( 0 == length $source) { die 'source error'; }
787             ##############
788             # check target
789             if( not exists $msg_hash->{'target'} ) { die "no target specified"; }
790             my $target_l = $msg_hash->{'target'};
791             if( (1 > @{$target_l}) || ( ('HASH' eq ref @{$target_l}[0]) && (1 > keys %{@{$target_l}[0]}) ) ) { die 'empty target tag'; }
792         }
793     };
794     if($@) {
795         daemon_log("$session_id ERROR: do not understand the message: $@", 1);
796         $msg = undef;
797         $msg_hash = undef;
798     }
800     return ($msg, $msg_hash);
804 sub check_outgoing_xml_validity {
805     my ($msg, $session_id) = @_;
807     my $msg_hash;
808     eval{
809         $msg_hash = $xml->XMLin($msg, ForceArray=>1);
811         ##############
812         # check header
813         my $header_l = $msg_hash->{'header'};
814         if( 1 != @{$header_l} ) {
815             die 'no or more than one headers specified';
816         }
817         my $header = @{$header_l}[0];
818         if( 0 == length $header) {
819             die 'header has length 0';
820         }
822         ##############
823         # check source
824         my $source_l = $msg_hash->{'source'};
825         if( 1 != @{$source_l} ) {
826             die 'no or more than 1 sources specified';
827         }
828         my $source = @{$source_l}[0];
829         if( 0 == length $source) {
830             die 'source has length 0';
831         }
833                 # Check if source contains hostname instead of ip address
834                 if($source =~ /^[a-z][\w\-\.]+:\d+$/i) {
835                         my ($hostname,$port) = split(/:/, $source);
836                         my $ip_address = inet_ntoa(scalar gethostbyname($hostname));
837                         if(defined($ip_address) && $ip_address =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/ && $port =~ /^\d+$/) {
838                                 # Write ip address to $source variable
839                                 $source = "$ip_address:$port";
840                                 $msg_hash->{source}[0] = $source ;
841                                 $msg =~ s/<source>.*<\/source>/<source>$source<\/source>/; 
842                         }
843                 }
844         unless( $source =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
845                 $source =~ /^GOSA$/i) {
846             die "source '$source' is neither a complete ip-address with port nor 'GOSA'";
847         }
848         
849         ##############
850         # check target  
851         my $target_l = $msg_hash->{'target'};
852         if( 0 == @{$target_l} ) {
853             die "no targets specified";
854         }
855         foreach my $target (@$target_l) {
856             if( 0 == length $target) {
857                 die "target has length 0";
858             }
859             unless( $target =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
860                     $target =~ /^GOSA$/i ||
861                     $target =~ /^\*$/ ||
862                     $target =~ /KNOWN_SERVER/i ||
863                     $target =~ /JOBDB/i ||
864                     $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 ){
865                 die "target '$target' is not a complete ip-address with port or a valid target name or a mac-address";
866             }
867         }
868     };
869     if($@) {
870         daemon_log("$session_id ERROR: outgoing msg is not gosa-si envelope conform: $@", 1);
871         daemon_log("$@ ".(defined($msg) && length($msg)>0)?$msg:"Empty Message", 1);
872         $msg_hash = undef;
873     }
875     return ($msg, $msg_hash);
879 sub input_from_known_server {
880     my ($input, $remote_ip, $session_id) = @_ ;  
881     my ($msg, $msg_hash, $module);
883     my $sql_statement= "SELECT * FROM known_server";
884     my $query_res = $known_server_db->select_dbentry( $sql_statement ); 
886     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
887         my $host_name = $hit->{hostname};
888         if( not $host_name =~ "^$remote_ip") {
889             next;
890         }
891         my $host_key = $hit->{hostkey};
892         daemon_log("$session_id DEBUG: input_from_known_server: host_name: $host_name", 14);
893         daemon_log("$session_id DEBUG: input_from_known_server: host_key: $host_key", 14);
895         # check if module can open msg envelope with module key
896         my ($tmp_msg, $tmp_msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
897         if( (!$tmp_msg) || (!$tmp_msg_hash) ) {
898             daemon_log("$session_id DEBUG: input_from_known_server: deciphering raise error", 14);
899             daemon_log("$@", 14);
900             next;
901         }
902         else {
903             $msg = $tmp_msg;
904             $msg_hash = $tmp_msg_hash;
905             $module = "ServerPackages";
906             daemon_log("$session_id DEBUG: check_key_and_xml_validity... ok", 14);
907             last;
908         }
909     }
911     if( (!$msg) || (!$msg_hash) || (!$module) ) {
912         daemon_log("$session_id DEBUG: Incoming message is not from a known server", 14);
913     }
914   
915     return ($msg, $msg_hash, $module);
919 sub input_from_known_client {
920     my ($input, $remote_ip, $session_id) = @_ ;  
921     my ($msg, $msg_hash, $module);
923     my $sql_statement= "SELECT * FROM known_clients";
924     my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
925     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
926         my $host_name = $hit->{hostname};
927         if( not $host_name =~ /^$remote_ip/) {
928                 next;
929                 }
930         my $host_key = $hit->{hostkey};
931         &daemon_log("$session_id DEBUG: input_from_known_client: host_name: $host_name", 14);
932         &daemon_log("$session_id DEBUG: input_from_known_client: host_key: $host_key", 14);
934         # check if module can open msg envelope with module key
935         ($msg, $msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
937         if( (!$msg) || (!$msg_hash) ) {
938             &daemon_log("$session_id DEGUG: input_from_known_client: deciphering raise error", 14);
939             next;
940         }
941         else {
942             $module = "ClientPackages";
943             daemon_log("$session_id DEBUG: check_key_and_xml_validity... ok", 14);
944             last;
945         }
946     }
948     if( (!$msg) || (!$msg_hash) || (!$module) ) {
949         &daemon_log("$session_id DEBUG: Incoming message is not from a known client", 14);
950     }
952     return ($msg, $msg_hash, $module);
956 sub input_from_unknown_host {
957         no strict "refs";
958         my ($input, $session_id) = @_ ;
959         my ($msg, $msg_hash, $module);
960         my $error_string;
962         my %act_modules = %$known_modules;
964         while( my ($mod, $info) = each(%act_modules)) {
966                 # check a key exists for this module
967                 my $module_key = ${$mod."_key"};
968                 if( not defined $module_key ) {
969                         if( $mod eq 'ArpHandler' ) {
970                                 next;
971                         }
972                         daemon_log("$session_id ERROR: no key specified in config file for $mod", 1);
973                         next;
974                 }
975                 daemon_log("$session_id DEBUG: $mod: $module_key", 14);
977                 # check if module can open msg envelope with module key
978                 ($msg, $msg_hash) = &check_key_and_xml_validity($input, $module_key, $session_id);
979                 if( (not defined $msg) || (not defined $msg_hash) ) {
980                         next;
981                 } else {
982                         $module = $mod;
983             daemon_log("$session_id DEBUG: check_key_and_xml_validity... ok", 18);
984                         last;
985                 }
986         }
988         if( (!$msg) || (!$msg_hash) || (!$module)) {
989                 daemon_log("$session_id DEBUG: Incoming message is not from an unknown host", 14);
990         }
992         return ($msg, $msg_hash, $module);
996 sub create_ciphering {
997     my ($passwd) = @_;
998         if((!defined($passwd)) || length($passwd)==0) {
999                 $passwd = "";
1000         }
1001     $passwd = substr(md5_hex("$passwd") x 32, 0, 32);
1002     my $iv = substr(md5_hex('GONICUS GmbH'),0, 16);
1003     my $my_cipher = Crypt::Rijndael->new($passwd , Crypt::Rijndael::MODE_CBC());
1004     $my_cipher->set_iv($iv);
1005     return $my_cipher;
1009 sub encrypt_msg {
1010     my ($msg, $key) = @_;
1011     my $my_cipher = &create_ciphering($key);
1012     my $len;
1013     {
1014             use bytes;
1015             $len= 16-length($msg)%16;
1016     }
1017     $msg = "\0"x($len).$msg;
1018     $msg = $my_cipher->encrypt($msg);
1019     chomp($msg = &encode_base64($msg));
1020     # there are no newlines allowed inside msg
1021     $msg=~ s/\n//g;
1022     return $msg;
1026 sub decrypt_msg {
1028     my ($msg, $key) = @_ ;
1029     $msg = &decode_base64($msg);
1030     my $my_cipher = &create_ciphering($key);
1031     $msg = $my_cipher->decrypt($msg); 
1032     $msg =~ s/\0*//g;
1033     return $msg;
1037 sub get_encrypt_key {
1038     my ($target) = @_ ;
1039     my $encrypt_key;
1040     my $error = 0;
1042     # target can be in known_server
1043     if( not defined $encrypt_key ) {
1044         my $sql_statement= "SELECT * FROM known_server WHERE hostname='$target'";
1045         my $query_res = $known_server_db->select_dbentry( $sql_statement ); 
1046         while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1047             my $host_name = $hit->{hostname};
1048             if( $host_name ne $target ) {
1049                 next;
1050             }
1051             $encrypt_key = $hit->{hostkey};
1052             last;
1053         }
1054     }
1056     # target can be in known_client
1057     if( not defined $encrypt_key ) {
1058         my $sql_statement= "SELECT * FROM known_clients WHERE hostname='$target'";
1059         my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
1060         while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1061             my $host_name = $hit->{hostname};
1062             if( $host_name ne $target ) {
1063                 next;
1064             }
1065             $encrypt_key = $hit->{hostkey};
1066             last;
1067         }
1068     }
1070     return $encrypt_key;
1074 #===  FUNCTION  ================================================================
1075 #         NAME:  open_socket
1076 #   PARAMETERS:  PeerAddr string something like 192.168.1.1 or 192.168.1.1:10000
1077 #                [PeerPort] string necessary if port not appended by PeerAddr
1078 #      RETURNS:  socket IO::Socket::INET
1079 #  DESCRIPTION:  open a socket to PeerAddr
1080 #===============================================================================
1081 sub open_socket {
1082     my ($PeerAddr, $PeerPort) = @_ ;
1083     if(defined($PeerPort)){
1084         $PeerAddr = $PeerAddr.":".$PeerPort;
1085     }
1086     my $socket;
1087     $socket = new IO::Socket::INET(PeerAddr => $PeerAddr,
1088             Porto => "tcp",
1089             Type => SOCK_STREAM,
1090             Timeout => 5,
1091             );
1092     if(not defined $socket) {
1093         return;
1094     }
1095 #    &daemon_log("DEBUG: open_socket: $PeerAddr", 7);
1096     return $socket;
1100 sub send_msg_to_target {
1101     my ($msg, $address, $encrypt_key, $msg_header, $session_id) = @_ ;
1102     my $error = 0;
1103     my $header;
1104     my $timestamp = &get_time();
1105     my $new_status;
1106     my $act_status;
1107     my ($sql_statement, $res);
1108   
1109     if( $msg_header ) {
1110         $header = "'$msg_header'-";
1111     } else {
1112         $header = "";
1113     }
1115         # Memorize own source address
1116         my $own_source_address = &get_local_ip_for_remote_ip(sprintf("%s", $address =~ /^([0-9\.]*?):.*$/));
1117         $own_source_address .= ":".$server_port;
1119         # Patch 0.0.0.0 source to real address
1120         $msg =~ s/<source>(0\.0\.0\.0):(\d*?)<\/source>/<source>$own_source_address<\/source>/s;
1121         # Patch GOSA source to real address and add forward_to_gosa tag
1122         $msg =~ s/<source>GOSA<\/source>/<source>$own_source_address<\/source> <forward_to_gosa>$own_source_address,$session_id<\/forward_to_gosa>/ ;
1124     # encrypt xml msg
1125     my $crypted_msg = &encrypt_msg($msg, $encrypt_key);
1127     # opensocket
1128     my $socket = &open_socket($address);
1129     if( !$socket ) {
1130         daemon_log("$session_id ERROR: Cannot open socket to host '$address'. Message processing aborted!", 1);
1131         $error++;
1132     }
1133     
1134     if( $error == 0 ) {
1135         # send xml msg
1136         print $socket $crypted_msg.";$own_source_address\n";
1137         daemon_log("$session_id INFO: send ".$header."msg to $address", 5);
1138         daemon_log("$session_id DEBUG: message:\n$msg", 12);
1139         
1140     }
1142     # close socket in any case
1143     if( $socket ) {
1144         close $socket;
1145     }
1147     if( $error > 0 ) { $new_status = "down"; }
1148     else { $new_status = $msg_header; }
1151     # known_clients
1152     $sql_statement = "SELECT * FROM $known_clients_tn WHERE hostname='$address'";
1153     $res = $known_clients_db->select_dbentry($sql_statement);
1154     if( keys(%$res) == 1) {
1155         $act_status = exists $res->{1}->{'status'} ? $res->{1}->{'status'} : "";
1156         if ($act_status eq "down" && $new_status eq "down") {
1157             $sql_statement = "DELETE FROM known_clients WHERE hostname='$address'";
1158             $res = $known_clients_db->del_dbentry($sql_statement);
1159             daemon_log("$session_id WARNING: failed 2x to send msg to host '$address', delete host from known_clients", 3);
1160         } else { 
1161             $sql_statement = "UPDATE known_clients SET status='$new_status', timestamp='$timestamp' WHERE hostname='$address'";
1162             $res = $known_clients_db->update_dbentry($sql_statement);
1163             if($new_status eq "down"){
1164                 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
1165             } else {
1166                 daemon_log("$session_id DEBUG: set '$address' from status '$act_status' to '$new_status'", 138);
1167             }
1168         }
1169     }
1171     # known_server
1172     $sql_statement = "SELECT * FROM $known_server_tn WHERE hostname='$address'";
1173     $res = $known_server_db->select_dbentry($sql_statement);
1174     if( keys(%$res) == 1) {
1175         $act_status = exists $res->{1}->{'status'} ? $res->{1}->{'status'} : "";
1176         if ($act_status eq "down" && $new_status eq "down") {
1177             $sql_statement = "DELETE FROM known_server WHERE hostname='$address'";
1178             $res = $known_server_db->del_dbentry($sql_statement);
1179             daemon_log("$session_id WARNING: failed 2x to send a message to host '$address', delete host from known_server", 3);
1180         } 
1181         else { 
1182             $sql_statement = "UPDATE known_server SET status='$new_status', timestamp='$timestamp' WHERE hostname='$address'";
1183             $res = $known_server_db->update_dbentry($sql_statement);
1184             if($new_status eq "down"){
1185                 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
1186             } else {
1187                 daemon_log("$session_id DEBUG: set '$address' from status '$act_status' to '$new_status'", 138);
1188             }
1189         }
1190     }
1191     return $error; 
1195 sub update_jobdb_status_for_send_msgs {
1196     my ($session_id, $answer, $error) = @_;
1197     if( $answer =~ /<jobdb_id>(\d+)<\/jobdb_id>/ ) {
1198                 &daemon_log("$session_id DEBUG: try to update job status", 138); 
1199         my $jobdb_id = $1;
1200     
1201         $answer =~ /<header>(.*)<\/header>/;
1202         my $job_header = $1;
1204         $answer =~ /<target>(.*)<\/target>/;
1205         my $job_target = $1;
1206             
1207         # Sending msg failed
1208         if( $error ) {
1210             # Set jobs to done, jobs do not need to deliver their message in any case
1211             if (($job_header eq "trigger_action_localboot")
1212                     ||($job_header eq "trigger_action_lock")
1213                     ||($job_header eq "trigger_action_halt") 
1214                     ) {
1215                 my $sql_statement = "UPDATE $job_queue_tn SET status='done' WHERE id=$jobdb_id";
1216                 my $res = $job_db->update_dbentry($sql_statement);
1217                 
1218             # Reactivate jobs, jobs need to deliver their message
1219             } elsif (($job_header eq "trigger_action_activate")
1220                     ||($job_header eq "trigger_action_update")
1221                     ||($job_header eq "trigger_action_reinstall") 
1222                     ||($job_header eq "trigger_activate_new")
1223                     ) {
1224                                                 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) {
1225                                                         &reactivate_job_with_delay($session_id, $job_target, $job_header, 30 );
1226                                                 } else {
1227                                                         # If we don't have the mac adress at this time, we use the plainname
1228                                                         my $plainname_result = $job_db->select_dbentry("SELECT plainname from jobs where id=$jobdb_id");
1229                                                         my $plainname = $job_target;
1230                                                         if ((keys(%$plainname_result) > 0) ) {
1231                                                                 $plainname = $plainname_result->{1}->{$job_target};
1232                                                         }
1233                                                         &reactivate_job_with_delay($session_id, $plainname, $job_header, 30 );
1234                                                 }
1235             # For all other messages
1236             } else {
1237                 my $sql_statement = "UPDATE $job_queue_tn ".
1238                     "SET status='error', result='can not deliver msg, please consult log file' ".
1239                     "WHERE id=$jobdb_id";
1240                 my $res = $job_db->update_dbentry($sql_statement);
1241             }
1243         # Sending msg was successful
1244         } else {
1245             # Set jobs localboot, lock, activate, halt, reboot and wake to done
1246             # jobs reinstall, update, inst_update do themself setting to done
1247             if (($job_header eq "trigger_action_localboot")
1248                     ||($job_header eq "trigger_action_lock")
1249                     ||($job_header eq "trigger_action_activate")
1250                     ||($job_header eq "trigger_action_halt") 
1251                     ||($job_header eq "trigger_action_reboot")
1252                     ||($job_header eq "trigger_action_wake")
1253                     ||($job_header eq "trigger_wake")
1254                     ) {
1256                 my $sql_statement = "UPDATE $job_queue_tn ".
1257                     "SET status='done' ".
1258                     "WHERE id=$jobdb_id AND status='processed'";
1259                 my $res = $job_db->update_dbentry($sql_statement);
1260             } else { 
1261                 &daemon_log("$session_id DEBUG: sending message succeed but cannot update job status.", 138); 
1262             } 
1263         } 
1264     } else { 
1265         &daemon_log("$session_id DEBUG: cannot update job status, msg has no jobdb_id-tag.", 138); 
1266     }
1269 sub reactivate_job_with_delay {
1270     my ($session_id, $target, $header, $delay) = @_ ;
1271     # 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
1272     
1273     if (not defined $delay) { $delay = 30 } ;
1274     my $delay_timestamp = &calc_timestamp(&get_time(), "plus", $delay);
1276     my $sql = "UPDATE $job_queue_tn Set timestamp='$delay_timestamp', status='waiting' WHERE (macaddress LIKE '$target' OR plainname LIKE '$target') AND headertag='$header'"; 
1277     my $res = $job_db->update_dbentry($sql);
1278     daemon_log("$session_id INFO: '$header'-job will be reactivated at '$delay_timestamp' ".
1279             "cause client '$target' is currently not available", 5);
1280     return;
1284 sub sig_handler {
1285         my ($kernel, $signal) = @_[KERNEL, ARG0] ;
1286         daemon_log("0 INFO got signal '$signal'", 1); 
1287         $kernel->sig_handled();
1288         return;
1292 sub msg_to_decrypt {
1293         my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
1294         my $session_id = $session->ID;
1295         my ($msg, $msg_hash, $module);
1296         my $error = 0;
1298         # fetch new msg out of @msgs_to_decrypt
1299         my $tmp_next_msg = shift @msgs_to_decrypt;
1300     my ($next_msg, $msg_source) = split(/;/, $tmp_next_msg);
1302         # msg is from a new client or gosa
1303         ($msg, $msg_hash, $module) = &input_from_unknown_host($next_msg, $session_id);
1305         # msg is from a gosa-si-server
1306         if(((!$msg) || (!$msg_hash) || (!$module)) && ($serverPackages_enabled eq "true")){
1307                 if (not defined $msg_source) 
1308                 {
1309                         # Only needed, to be compatible with older gosa-si-server versions
1310                         ($msg, $msg_hash, $module) = &input_from_known_server($next_msg, $heap->{'remote_ip'}, $session_id);
1311                 }
1312                 else
1313                 {
1314                         ($msg, $msg_hash, $module) = &input_from_known_server($next_msg, $msg_source, $session_id);
1315                 }
1316         }
1317         # msg is from a gosa-si-client
1318         if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1319                 if (not defined $msg_source) 
1320                 {
1321                         # Only needed, to be compatible with older gosa-si-server versions
1322                         ($msg, $msg_hash, $module) = &input_from_known_client($next_msg, $heap->{'remote_ip'}, $session_id);
1323                 }
1324                 else
1325                 {
1326                         ($msg, $msg_hash, $module) = &input_from_known_client($next_msg, $msg_source, $session_id);
1327                 }
1328         }
1329         # an error occurred
1330         if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1331                 # If an incoming msg could not be decrypted (maybe a wrong key), decide if msg comes from a client 
1332                 # or a server.  In case of a client, send a ping. If the client could not understand a msg from its 
1333                 # server the client cause a re-registering process. In case of a server, decrease update_time in kown_server_db
1334                 # and trigger a re-registering process for servers
1335                 if (defined $msg_source && $msg_source =~ /:$server_port$/ && $serverPackages_enabled eq "true")
1336                 {
1337                         daemon_log("$session_id WARNING: Cannot understand incoming msg from server '$msg_source'. Cause re-registration process for servers.", 3);
1338                         my $update_statement = "UPDATE $known_server_tn SET update_time='19700101000000' WHERE hostname='$msg_source'"; 
1339                         daemon_log("$session_id DEBUG: $update_statement", 7);
1340                         my $upadte_res = $known_server_db->exec_statement($update_statement);
1341                         $kernel->yield("register_at_foreign_servers");
1342                 }
1343                 elsif ((defined $msg_source) && (not $msg_source =~ /:$server_port$/))
1344                 {
1345                         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);
1346                         #my $remote_ip = $heap->{'remote_ip'};
1347                         #my $remote_port = $heap->{'remote_port'};
1348                         my $ping_msg = "<xml> <header>gosa_ping</header> <source>$server_address</source><target>$msg_source</target></xml>";
1349                         my ($test_error, $test_error_string) = &send_msg_to_target($ping_msg, "$msg_source", "dummy-key", "gosa_ping", $session_id);
1350                         daemon_log("$session_id WARNING: Sending msg to cause re-registering: $ping_msg", 3);
1351                 }
1352                 else
1353                 {
1354                         my $foreign_host = defined $msg_source ? $msg_source : $heap->{'remote_ip'};
1355                         daemon_log("$session_id ERROR: Incoming message from host '$foreign_host' cannot be understood. Processing aborted!", 1);
1356                         daemon_log("$session_id DEBUG: Aborted message: $tmp_next_msg", 11);
1357                 }
1359                 $error++
1360         }
1363         my $header;
1364         my $target;
1365         my $source;
1366         my $done = 0;
1367         my $sql;
1368         my $res;
1370         # check whether this message should be processed here
1371         if ($error == 0) {
1372                 $header = @{$msg_hash->{'header'}}[0];
1373                 $target = @{$msg_hash->{'target'}}[0];
1374                 $source = @{$msg_hash->{'source'}}[0];
1375                 my $not_found_in_known_clients_db = 0;
1376                 my $not_found_in_known_server_db = 0;
1377                 my $not_found_in_foreign_clients_db = 0;
1378                 my $local_address;
1379                 my $local_mac;
1380                 my ($target_ip, $target_port) = split(':', $target);
1382                 # Determine the local ip address if target is an ip address
1383                 if ($target =~ /^\d+\.\d+\.\d+\.\d+:\d+$/) {
1384                         $local_address = &get_local_ip_for_remote_ip($target_ip).":$server_port";
1385                 } else {
1386                         $local_address = $server_address;
1387                 }
1389                 # Determine the local mac address if target is a mac address
1390                 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) {
1391                         my $loc_ip = &get_local_ip_for_remote_ip($heap->{'remote_ip'});
1392                         my $network_interface= &get_interface_for_ip($loc_ip);
1393                         $local_mac = &get_mac_for_interface($network_interface);
1394                 } else {
1395                         $local_mac = $server_mac_address;
1396                 }
1398                 # target and source is equal to GOSA -> process here
1399                 if (not $done) {
1400                         if ($target eq "GOSA" && $source eq "GOSA") {
1401                                 $done = 1;                    
1402                                 &daemon_log("$session_id DEBUG: target and source is 'GOSA' -> process '$header' here", 11);
1403                         }
1404                 }
1406                 # target is own address without forward_to_gosa-tag -> process here
1407                 if (not $done) {
1408                         #if ((($target eq $local_address) || ($target eq $local_mac) ) && (not exists $msg_hash->{'forward_to_gosa'})) {
1409                         if (($target eq $local_address) && (not exists $msg_hash->{'forward_to_gosa'})) {
1410                                 $done = 1;
1411                                 if ($source eq "GOSA") {
1412                                         $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1413                                 }
1414                                 &daemon_log("$session_id DEBUG: target is own address without forward_to_gosa-tag -> process '$header' here", 11);
1415                         }
1416                 }
1418                 # target is own address with forward_to_gosa-tag not pointing to myself -> process here
1419                 if (not $done) {
1420                         my $forward_to_gosa =  @{$msg_hash->{'forward_to_gosa'}}[0];
1421                         my $gosa_at;
1422                         my $gosa_session_id;
1423                         if (($target eq $local_address) && (defined $forward_to_gosa)){
1424                                 my ($gosa_at, $gosa_session_id) = split(/,/, $forward_to_gosa);
1425                                 if ($gosa_at ne $local_address) {
1426                                         $done = 1;
1427                                         &daemon_log("$session_id DEBUG: target is own address with forward_to_gosa-tag not pointing to myself -> process '$header' here", 11); 
1428                                 }
1429                         }
1430                 }
1432                 # Target is a client address and there is a processing function within a plugin -> process loaclly
1433                 if (not $done)
1434                 {
1435                         # Check if target is a client address
1436                         $sql = "SELECT * FROM $known_clients_tn WHERE (hostname='$target' OR macaddress LIKE '$target')"; 
1437                         $res = $known_clients_db->select_dbentry($sql);
1438                         if ((keys(%$res) > 0) ) 
1439                         {
1440                                 my $hostname = $res->{1}->{'hostname'};
1441                                 my $reduced_header = $header;
1442                                 $reduced_header =~ s/gosa_//;
1443                                 # Check if there is a processing function within a plugin
1444                                 if (exists $known_functions->{$reduced_header}) 
1445                                 {
1446                                         $msg =~ s/<target>\S*<\/target>/<target>$hostname<\/target>/;
1447                                         $done = 1;
1448                                         &daemon_log("$session_id DEBUG: Target is client address with processing function within a plugin -> process '$header' here", 11);
1449                                 }
1450                         }
1451                 }
1453                 # If header has a 'job_' prefix, do always process message locally
1454                 # which means put it into job queue
1455                 if ((not $done) && ($header =~ /job_/))
1456                 {
1457                         $done = 1;
1458                         &daemon_log("$session_id DEBUG: Header has a 'job_' prefix. Put it into job queue. -> process '$header' here", 11);
1459                 }
1461                 # if message should be processed here -> add message to incoming_db
1462                 if ($done) {
1463                         # if a 'job_' or a 'gosa_' message comes from a foreign server, fake module from
1464                         # ServerPackages to GosaPackages so gosa-si-server knows how to process this kind of messages
1465                         if ($header =~ /^gosa_/ || $header =~ /^job_/) {
1466                                 $module = "GosaPackages";
1467                         }
1469                         my $res = $incoming_db->add_dbentry( {table=>$incoming_tn,
1470                                         primkey=>[],
1471                                         headertag=>$header,
1472                                         targettag=>$target,
1473                                         xmlmessage=>&encode_base64($msg),
1474                                         timestamp=>&get_time,
1475                                         module=>$module,
1476                                         sessionid=>$session_id,
1477                                 } );
1478                         $kernel->yield('watch_for_next_tasks');
1479                 }
1481                 # target is own address with forward_to_gosa-tag pointing at myself -> forward to gosa
1482                 if (not $done) {
1483                         my $forward_to_gosa =  @{$msg_hash->{'forward_to_gosa'}}[0];
1484                         my $gosa_at;
1485                         my $gosa_session_id;
1486                         if (($target eq $local_address) && (defined $forward_to_gosa)){
1487                                 my ($gosa_at, $gosa_session_id) = split(/,/, $forward_to_gosa);
1488                                 if ($gosa_at eq $local_address) {
1489                                         my $session_reference = $kernel->ID_id_to_session($gosa_session_id);
1490                                         if( defined $session_reference ) {
1491                                                 $heap = $session_reference->get_heap();
1492                                         }
1493                                         if(exists $heap->{'client'}) {
1494                                                 $msg = &encrypt_msg($msg, $GosaPackages_key);
1495                                                 $heap->{'client'}->put($msg);
1496                                                 &daemon_log("$session_id DEBUG: incoming '$header' message forwarded to GOsa", 11); 
1497                                         }
1498                                         $done = 1;
1499                                         &daemon_log("$session_id DEBUG: target is own address with forward_to_gosa-tag pointing at myself -> forward '$header' to gosa", 11);
1500                                 }
1501                         }
1503                 }
1505                 # target is a client address in known_clients -> forward to client
1506                 if (not $done) {
1507                         $sql = "SELECT * FROM $known_clients_tn WHERE (hostname='$target' OR macaddress LIKE '$target')"; 
1508                         $res = $known_clients_db->select_dbentry($sql);
1509                         if (keys(%$res) > 0) 
1510                         {
1511                                 $done = 1; 
1512                                 &daemon_log("$session_id DEBUG: target is a client address in known_clients -> forward '$header' to client", 11);
1513                                 my $hostkey = $res->{1}->{'hostkey'};
1514                                 my $hostname = $res->{1}->{'hostname'};
1515                                 $msg =~ s/<target>\S*<\/target>/<target>$hostname<\/target>/;
1516                                 $msg =~ s/<header>gosa_/<header>/;
1517                                 my $error= &send_msg_to_target($msg, $hostname, $hostkey, $header, $session_id);
1518                                 if ($error) {
1519                                         &daemon_log("$session_id ERROR: Some problems occurred while trying to send msg to client '$hostname': $msg", 1);
1520                                 }
1521                         } 
1522                         else 
1523                         {
1524                                 $not_found_in_known_clients_db = 1;
1525                         }
1526                 }
1528                 # target is a client address in foreign_clients -> forward to registration server
1529                 if (not $done) {
1530                         $sql = "SELECT * FROM $foreign_clients_tn WHERE (hostname='$target' OR macaddress LIKE '$target')";
1531                         $res = $foreign_clients_db->select_dbentry($sql);
1532                         if (keys(%$res) > 0) {
1533                                 my $hostname = $res->{1}->{'hostname'};
1534                                 my ($host_ip, $host_port) = split(/:/, $hostname);
1535                                 my $local_address =  &get_local_ip_for_remote_ip($host_ip).":$server_port";
1536                                 my $regserver = $res->{1}->{'regserver'};
1537                                 my $sql = "SELECT * FROM $known_server_tn WHERE hostname='$regserver'"; 
1538                                 my $res = $known_server_db->select_dbentry($sql);
1539                                 if (keys(%$res) > 0) {
1540                                         my $regserver_key = $res->{1}->{'hostkey'};
1541                                         $msg =~ s/<source>GOSA<\/source>/<source>$local_address<\/source>/;
1542                                         $msg =~ s/<target>$target<\/target>/<target>$hostname<\/target>/;
1543                                         if ($source eq "GOSA") {
1544                                                 $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1545                                         }
1546                                         my $error= &send_msg_to_target($msg, $regserver, $regserver_key, $header, $session_id);
1547                                         if ($error) {
1548                                                 &daemon_log("$session_id ERROR: some problems occurred while trying to send msg to registration server: $msg", 1); 
1549                                         }
1550                                 }
1551                                 $done = 1;
1552                                 &daemon_log("$session_id DEBUG: target is a client address in foreign_clients -> forward '$header' to registration server", 11);
1553                         } else {
1554                                 $not_found_in_foreign_clients_db = 1;
1555                         }
1556                 }
1558                 # target is a server address -> forward to server
1559                 if (not $done) {
1560                         $sql = "SELECT * FROM $known_server_tn WHERE (hostname='$target' OR macaddress LIKE '$target')";
1561                         $res = $known_server_db->select_dbentry($sql);
1562                         if (keys(%$res) > 0) {
1563                                 my $hostkey = $res->{1}->{'hostkey'};
1565                                 if ($source eq "GOSA") {
1566                                         $msg =~ s/<source>GOSA<\/source>/<source>$local_address<\/source>/;
1567                                         $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1569                                 }
1571                                 &send_msg_to_target($msg, $target, $hostkey, $header, $session_id);
1572                                 $done = 1;
1573                                 &daemon_log("$session_id DEBUG: target is a server address -> forward '$header' to server", 11);
1574                         } else {
1575                                 $not_found_in_known_server_db = 1;
1576                         }
1577                 }
1580                 # target is not in foreign_clients_db, known_server_db or known_clients_db, maybe it is a complete new one -> process here
1581                 if ( $not_found_in_foreign_clients_db 
1582                         && $not_found_in_known_server_db
1583                         && $not_found_in_known_clients_db) {
1584                         &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);
1585             if ($header =~ /^gosa_/ || $header =~ /^job_/) { 
1586                 $module = "GosaPackages"; 
1587             }
1588                         my $res = $incoming_db->add_dbentry( {table=>$incoming_tn,
1589                                         primkey=>[],
1590                                         headertag=>$header,
1591                                         targettag=>$target,
1592                                         xmlmessage=>&encode_base64($msg),
1593                                         timestamp=>&get_time,
1594                                         module=>$module,
1595                                         sessionid=>$session_id,
1596                                 } );
1597                         $done = 1;
1598                         $kernel->yield('watch_for_next_tasks');
1599                 }
1602                 if (not $done) {
1603                         daemon_log("$session_id ERROR: do not know what to do with this message: $msg", 1);
1604                         if ($source eq "GOSA") {
1605                                 my %data = ('error_msg' => &encode_base64($msg), 'error_string' => "Do not know what to do with this message!");
1606                                 my $error_msg = &build_msg("error", $local_address, "GOSA", \%data ); 
1608                                 my $session_reference = $kernel->ID_id_to_session($session_id);
1609                                 if( defined $session_reference ) {
1610                                         $heap = $session_reference->get_heap();
1611                                 }
1612                                 if(exists $heap->{'client'}) {
1613                                         $error_msg = &encrypt_msg($error_msg, $GosaPackages_key);
1614                                         $heap->{'client'}->put($error_msg);
1615                                 }
1616                         }
1617                 }
1619         }
1621         return;
1625 sub next_task {
1626     my ($session, $heap, $task) = @_[SESSION, HEAP, ARG0, ARG1];
1627     my $running_task = POE::Wheel::Run->new(
1628             Program => sub { process_task($session, $heap, $task) },
1629             StdioFilter => POE::Filter::Reference->new(),
1630             StdoutEvent  => "task_result",
1631             StderrEvent  => "task_debug",
1632             CloseEvent   => "task_done",
1633             );
1634     $heap->{task}->{ $running_task->ID } = $running_task;
1637 sub handle_task_result {
1638     my ($kernel, $heap, $result) = @_[KERNEL, HEAP, ARG0];
1639     my $client_answer = $result->{'answer'};
1640     if( $client_answer =~ s/session_id=(\d+)$// ) {
1641         my $session_id = $1;
1642         if( defined $session_id ) {
1643             my $session_reference = $kernel->ID_id_to_session($session_id);
1644             if( defined $session_reference ) {
1645                 $heap = $session_reference->get_heap();
1646             }
1647         }
1649         if(exists $heap->{'client'}) {
1650             $heap->{'client'}->put($client_answer);
1651         }
1652     }
1653     $kernel->sig(CHLD => "child_reap");
1656 sub handle_task_debug {
1657     my $result = $_[ARG0];
1658     print STDERR "$result\n";
1661 sub handle_task_done {
1662     my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
1663     delete $heap->{task}->{$task_id};
1664         if (exists $heap->{ldap_handle}->{$task_id}) {
1665                 &release_ldap_handle($heap->{ldap_handle}->{$task_id});
1666         }
1669 sub process_task {
1670     no strict "refs";
1671     #CHECK: Not @_[...]?
1672     my ($session, $heap, $task) = @_;
1673     my $error = 0;
1674     my $answer_l;
1675     my ($answer_header, @answer_target_l, $answer_source);
1676     my $client_answer = "";
1678     # prepare all variables needed to process message
1679     #my $msg = $task->{'xmlmessage'};
1680     my $msg = &decode_base64($task->{'xmlmessage'});
1681     my $incoming_id = $task->{'id'};
1682     my $module = $task->{'module'};
1683     my $header =  $task->{'headertag'};
1684     my $session_id = $task->{'sessionid'};
1685                 my $msg_hash;
1686                 eval {
1687         $msg_hash = $xml->XMLin($msg, ForceArray=>1);
1688                 }; 
1689                 daemon_log("ERROR: XML failure '$@'") if ($@);
1690     my $source = @{$msg_hash->{'source'}}[0];
1691     
1692     # set timestamp of incoming client uptodate, so client will not 
1693     # be deleted from known_clients because of expiration
1694     my $cur_time = &get_time();
1695     my $sql = "UPDATE $known_clients_tn SET timestamp='$cur_time' WHERE hostname='$source'"; 
1696     my $res = $known_clients_db->exec_statement($sql);
1698     ######################
1699     # process incoming msg
1700     if( $error == 0) {
1701         daemon_log("$session_id INFO: Incoming msg (session_id=$session_id) with header '".@{$msg_hash->{'header'}}[0]."'", 5); 
1702         daemon_log("$session_id DEBUG: Processing module ".$module, 26);
1703         $answer_l = &{ $module."::process_incoming_msg" }($msg, $msg_hash, $session_id);
1705         if ( 0 < @{$answer_l} ) {
1706             my $answer_str = join("\n", @{$answer_l});
1707                         my @headers; 
1708             while ($answer_str =~ /<header>(\w+)<\/header>/g) {
1709                                 push(@headers, $1);
1710             }
1711                         daemon_log("$session_id DEBUG: got answer message(s) with header: '".join("', '", @headers)."'", 26);
1712         } else {
1713             daemon_log("$session_id DEBUG: $module: got no answer from module!" ,26);
1714         }
1716     }
1717     if( !$answer_l ) { $error++ };
1719     ########
1720     # answer
1721     if( $error == 0 ) {
1723         foreach my $answer ( @{$answer_l} ) {
1724             # check outgoing msg to xml validity
1725             my ($answer, $answer_hash) = &check_outgoing_xml_validity($answer, $session_id);
1726             if( not defined $answer_hash ) { next; }
1727             
1728             $answer_header = @{$answer_hash->{'header'}}[0];
1729             @answer_target_l = @{$answer_hash->{'target'}};
1730             $answer_source = @{$answer_hash->{'source'}}[0];
1732             # deliver msg to all targets 
1733             foreach my $answer_target ( @answer_target_l ) {
1735                 # targets of msg are all gosa-si-clients in known_clients_db
1736                 if( $answer_target eq "*" ) {
1737                     # answer is for all clients
1738                     my $sql_statement= "SELECT * FROM known_clients";
1739                     my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
1740                     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1741                         my $host_name = $hit->{hostname};
1742                         my $host_key = $hit->{hostkey};
1743                         my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1744                         &update_jobdb_status_for_send_msgs($session_id, $answer, $error);
1745                     }
1746                 }
1748                 # targets of msg are all gosa-si-server in known_server_db
1749                 elsif( $answer_target eq "KNOWN_SERVER" ) {
1750                     # answer is for all server in known_server
1751                     my $sql_statement= "SELECT * FROM $known_server_tn";
1752                     my $query_res = $known_server_db->select_dbentry( $sql_statement ); 
1753                     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1754                         my $host_name = $hit->{hostname};
1755                         my $host_key = $hit->{hostkey};
1756                         $answer =~ s/<target>\S+<\/target>/<target>$host_name<\/target>/g;
1757                         my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1758                         &update_jobdb_status_for_send_msgs($session_id, $answer, $error);
1759                     }
1760                 }
1762                 # target of msg is GOsa
1763                                 elsif( $answer_target eq "GOSA" ) {
1764                                         my $session_id = ($1) if $answer =~ /<session_id>(\d+?)<\/session_id>/;
1765                                         my $add_on = "";
1766                     if( defined $session_id ) {
1767                         $add_on = ".session_id=$session_id";
1768                     }
1769                                         my $header = ($1) if $answer =~ /<header>(\S*)<\/header>/;
1770                                         daemon_log("$session_id INFO: send ".$header." message to GOsa", 5);
1771                                         daemon_log("$session_id DEBUG: message:\n$answer", 12);
1772                     # answer is for GOSA and has to returned to connected client
1773                     my $gosa_answer = &encrypt_msg($answer, $GosaPackages_key);
1774                     $client_answer = $gosa_answer.$add_on;
1775                 }
1777                 # target of msg is job queue at this host
1778                 elsif( $answer_target eq "JOBDB") {
1779                     $answer =~ /<header>(\S+)<\/header>/;   
1780                     my $header;
1781                     if( defined $1 ) { $header = $1; }
1782                     my $error = &send_msg_to_target($answer, $server_address, $GosaPackages_key, $header, $session_id);
1783                     &update_jobdb_status_for_send_msgs($session_id, $answer, $error);
1784                 }
1786                 # Target of msg is a mac address
1787                 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 ) {
1788                     daemon_log("$session_id DEBUG: target is mac address '$answer_target', looking for host in known_clients and foreign_clients", 138);
1790                     # Looking for macaddress in known_clients
1791                     my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$answer_target'";
1792                     my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1793                     my $found_ip_flag = 0;
1794                     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1795                         my $host_name = $hit->{hostname};
1796                         my $host_key = $hit->{hostkey};
1797                         $answer =~ s/$answer_target/$host_name/g;
1798                         daemon_log("$session_id INFO: found host '$host_name', associated to '$answer_target'", 5);
1799                         my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1800                         &update_jobdb_status_for_send_msgs($session_id, $answer, $error);
1801                         $found_ip_flag++ ;
1802                     }   
1804                     # Looking for macaddress in foreign_clients
1805                     if ($found_ip_flag == 0) {
1806                         my $sql = "SELECT * FROM $foreign_clients_tn WHERE macaddress LIKE '$answer_target'";
1807                         my $res = $foreign_clients_db->select_dbentry($sql);
1808                         while( my ($hit_num, $hit) = each %{ $res } ) {
1809                             my $host_name = $hit->{hostname};
1810                             my $reg_server = $hit->{regserver};
1811                             daemon_log("$session_id INFO: found host '$host_name' with mac '$answer_target', registered at '$reg_server'", 5);
1812                             
1813                             # Fetch key for reg_server
1814                             my $reg_server_key;
1815                             my $sql = "SELECT * FROM $known_server_tn WHERE hostname='$reg_server'";
1816                             my $res = $known_server_db->select_dbentry($sql);
1817                             if (exists $res->{1}) {
1818                                 $reg_server_key = $res->{1}->{'hostkey'}; 
1819                             } else {
1820                                 daemon_log("$session_id ERROR: cannot find hostkey for '$host_name' in '$known_server_tn'", 1); 
1821                                 daemon_log("$session_id ERROR: unable to forward answer to correct registration server, processing is aborted!", 1); 
1822                                 $reg_server_key = undef;
1823                             }
1825                             # Send answer to server where client is registered
1826                             if (defined $reg_server_key) {
1827                                 $answer =~ s/$answer_target/$host_name/g;
1828                                 my $error = &send_msg_to_target($answer, $reg_server, $reg_server_key, $answer_header, $session_id);
1829                                 &update_jobdb_status_for_send_msgs($session_id, $answer, $error);
1830                                 $found_ip_flag++ ;
1831                             }
1832                         }
1833                     }
1835                     # No mac to ip matching found
1836                     if( $found_ip_flag == 0) {
1837                         daemon_log("$session_id WARNING: no host found in known_clients or foreign_clients with mac address '$answer_target'", 3);
1838                         &reactivate_job_with_delay($session_id, $answer_target, $answer_header, 30);
1839                     }
1841                 # Answer is for one specific host   
1842                 } else {
1843                     # get encrypt_key
1844                     my $encrypt_key = &get_encrypt_key($answer_target);
1845                     if( not defined $encrypt_key ) {
1846                         # unknown target
1847                         daemon_log("$session_id WARNING: unknown target '$answer_target'", 3);
1848                         next;
1849                     }
1850                     my $error = &send_msg_to_target($answer, $answer_target, $encrypt_key, $answer_header,$session_id);
1851                     &update_jobdb_status_for_send_msgs($session_id, $answer, $error);
1852                 }
1853             }
1854         }
1855     }
1857     my $filter = POE::Filter::Reference->new();
1858     my %result = ( 
1859             status => "seems ok to me",
1860             answer => $client_answer,
1861             );
1863     my $output = $filter->put( [ \%result ] );
1864     print @$output;
1869 sub session_start {
1870     my ($kernel) = $_[KERNEL];
1871     $global_kernel = $kernel;
1872     $kernel->yield('register_at_foreign_servers');
1873         $kernel->yield('create_fai_server_db', $fai_server_tn );
1874         $kernel->yield('create_fai_release_db', $fai_release_tn );
1875         $kernel->sig(USR1 => "sig_handler");
1876         $kernel->sig(USR2 => "recreate_packages_db");
1877         $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1878         $kernel->delay_set('watch_for_done_jobs', $job_queue_loop_delay); 
1879     $kernel->delay_set('watch_for_modified_jobs', $modified_jobs_loop_delay); 
1880         $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay);
1881     $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay);
1882         $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay);
1883     $kernel->delay_set('watch_for_old_known_clients', $job_queue_loop_delay);
1885     # Start opsi check
1886     if ($opsi_enabled eq "true") {
1887         $kernel->delay_set('watch_for_opsi_jobs', $job_queue_opsi_delay); 
1888     }
1893 sub watch_for_done_jobs {
1894         my $kernel = $_[KERNEL];
1896         my $sql_statement = "SELECT * FROM ".$job_queue_tn." WHERE ((status='done') AND (modified='0'))";
1897         my $res = $job_db->select_dbentry( $sql_statement );
1899         while( my ($number, $hit) = each %{$res} ) 
1900         {
1901                 # Non periodical jobs can be deleted.
1902                 if ($hit->{periodic} eq "none")
1903                 {
1904                         my $jobdb_id = $hit->{id};
1905                         my $sql_statement = "DELETE FROM $job_queue_tn WHERE id=$jobdb_id"; 
1906                         my $res = $job_db->del_dbentry($sql_statement); 
1907                 }
1909                 # Periodical jobs should not be deleted but reactivated with new timestamp instead.
1910                 else
1911                 {
1912                         my ($p_time, $periodic) = split("_", $hit->{periodic});
1913                         my $reactivated_ts = $hit->{timestamp};
1914                         my $act_ts = int(&get_time());
1915                         while ($act_ts > int($reactivated_ts))   # Redo calculation to avoid multiple jobs in the past
1916                         {
1917                                 $reactivated_ts = &calc_timestamp($reactivated_ts, "plus", $p_time, $periodic);
1918                         }
1919                         my $sql = "UPDATE $job_queue_tn SET status='waiting', timestamp='$reactivated_ts' WHERE id='".$hit->{id}."'"; 
1920                         my $res = $job_db->exec_statement($sql);
1921                         &daemon_log("J INFO: Update periodical job '".$hit->{headertag}."' for client '".$hit->{targettag}."'. New execution time '$reactivated_ts'.", 5);
1922                 }
1923         }
1925         $kernel->delay_set('watch_for_done_jobs',$job_queue_loop_delay);
1929 sub watch_for_opsi_jobs {
1930     my ($kernel) = $_[KERNEL];
1931     # 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 
1932     # opsi install job is to parse the xml message. There is still the correct header.
1933     my $sql_statement = "SELECT * FROM ".$job_queue_tn." WHERE ((xmlmessage LIKE '%opsi_install_client</header>%') AND (status='processing') AND (siserver='localhost'))";
1934         my $res = $job_db->select_dbentry( $sql_statement );
1936     # Ask OPSI for an update of the running jobs
1937     while (my ($id, $hit) = each %$res ) {
1938         # Determine current parameters of the job
1939         my $hostId = $hit->{'plainname'};
1940         my $macaddress = $hit->{'macaddress'};
1941         my $progress = $hit->{'progress'};
1943         my $result= {};
1944         
1945         # For hosts, only return the products that are or get installed
1946         my $callobj;
1947         $callobj = {
1948             method  => 'getProductStates_hash',
1949             params  => [ $hostId ],
1950             id  => 1,
1951         };
1952         
1953         my $hres = $opsi_client->call($opsi_url, $callobj);
1954                 # TODO : move all opsi relevant statments to opsi plugin
1955                 # The following 3 lines must be tested befor they can replace the rubbish above and below !!!
1956                 #my ($res, $err) = &opsi_com::_getProductStates_hash(hostId=>$hostId)
1957                 #if (not $err) {
1958                 #       my $htmp = $res->{$hostId};
1959                 # 
1960         if (not &check_opsi_res($hres)) {
1961             my $htmp= $hres->result->{$hostId};
1962             # Check state != not_installed or action == setup -> load and add
1963             my $products= 0;
1964             my $installed= 0;
1965             my $installing = 0;
1966             my $error= 0;  
1967             my @installed_list;
1968             my @error_list;
1969             my $act_status = "none";
1970             foreach my $product (@{$htmp}){
1972                 if ($product->{'installationStatus'} ne "not_installed" or
1973                         $product->{'actionRequest'} eq "setup"){
1975                     # Increase number of products for this host
1976                     $products++;
1977         
1978                     if ($product->{'installationStatus'} eq "failed"){
1979                         $result->{$product->{'productId'}}= "error";
1980                         unshift(@error_list, $product->{'productId'});
1981                         $error++;
1982                     }
1983                     if ($product->{'installationStatus'} eq "installed" && $product->{'actionRequest'}  eq "none"){
1984                         $result->{$product->{'productId'}}= "installed";
1985                         unshift(@installed_list, $product->{'productId'});
1986                         $installed++;
1987                     }
1988                     if ($product->{'installationStatus'} eq "installing"){
1989                         $result->{$product->{'productId'}}= "installing";
1990                         $installing++;
1991                         $act_status = "installing - ".$product->{'productId'};
1992                     }
1993                 }
1994             }
1995         
1996             # Estimate "rough" progress, avoid division by zero
1997             if ($products == 0) {
1998                 $result->{'progress'}= 0;
1999             } else {
2000                 $result->{'progress'}= int($installed * 100 / $products);
2001             }
2003             # Set updates in job queue
2004             if ((not $error) && (not $installing) && ($installed)) {
2005                 $act_status = "installed - ".join(", ", @installed_list);
2006             }
2007             if ($error) {
2008                 $act_status = "error - ".join(", ", @error_list);
2009             }
2010             if ($progress ne $result->{'progress'} ) {
2011                 # Updating progress and result 
2012                 my $update_statement = "UPDATE $job_queue_tn SET modified='1', progress='".$result->{'progress'}."', result='$act_status' WHERE macaddress LIKE '$macaddress' AND siserver='localhost'";
2013                 my $update_res = $job_db->update_dbentry($update_statement);
2014             }
2015             if ($progress eq 100) { 
2016                 # Updateing status
2017                 my $done_statement = "UPDATE $job_queue_tn SET modified='1', ";
2018                 if ($error) {
2019                     $done_statement .= "status='error'";
2020                 } else {
2021                     $done_statement .= "status='done'";
2022                 }
2023                 $done_statement .= " WHERE macaddress LIKE '$macaddress' AND siserver='localhost'";
2024                 my $done_res = $job_db->update_dbentry($done_statement);
2025             }
2028         }
2029     }
2031     $kernel->delay_set('watch_for_opsi_jobs', $job_queue_opsi_delay);
2035 # If a job got an update or was modified anyway, send to all other si-server an update message of this jobs.
2036 sub watch_for_modified_jobs {
2037     my ($kernel,$heap) = @_[KERNEL, HEAP];
2039     my $sql_statement = "SELECT * FROM $job_queue_tn WHERE (modified='1')"; 
2040     my $res = $job_db->select_dbentry( $sql_statement );
2041     
2042     # if db contains no jobs which should be update, do nothing
2043     if (keys %$res != 0) {
2045         if ($job_synchronization  eq "true") {
2046             # make out of the db result a gosa-si message   
2047             my $update_msg = &db_res2si_msg ($res, "foreign_job_updates", "KNOWN_SERVER", "MY_LOCAL_ADDRESS");
2048  
2049             # update all other SI-server
2050             &inform_all_other_si_server($update_msg);
2051         }
2053         # set jobs all jobs to modified = 0, wait until the next modification for updates of other si-server
2054         $sql_statement = "UPDATE $job_queue_tn SET modified='0' ";
2055         $res = $job_db->update_dbentry($sql_statement);
2056     }
2058     $kernel->delay_set('watch_for_modified_jobs', $modified_jobs_loop_delay);
2062 sub watch_for_new_jobs {
2063         if($watch_for_new_jobs_in_progress == 0) {
2064                 $watch_for_new_jobs_in_progress = 1;
2065                 my ($kernel,$heap) = @_[KERNEL, HEAP];
2067                 # check gosa job queue for jobs with executable timestamp
2068                 my $timestamp = &get_time();
2069                 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE siserver='localhost' AND status='waiting' AND (CAST(timestamp AS UNSIGNED)) < $timestamp ORDER BY timestamp";
2070                 my $res = $job_db->exec_statement( $sql_statement );
2072                 # Merge all new jobs that would do the same actions
2073                 my @drops;
2074                 my $hits;
2075                 foreach my $hit (reverse @{$res} ) {
2076                         my $macaddress= lc @{$hit}[8];
2077                         my $headertag= @{$hit}[5];
2078                         if(
2079                                 defined($hits->{$macaddress}) &&
2080                                 defined($hits->{$macaddress}->{$headertag}) &&
2081                                 defined($hits->{$macaddress}->{$headertag}[0])
2082                         ) {
2083                                 push @drops, "DELETE FROM $job_queue_tn WHERE id = $hits->{$macaddress}->{$headertag}[0]";
2084                         }
2085                         $hits->{$macaddress}->{$headertag}= $hit;
2086                 }
2088                 # Delete new jobs with a matching job in state 'processing'
2089                 foreach my $macaddress (keys %{$hits}) {
2090                         foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
2091                                 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
2092                                 if(defined($jobdb_id)) {
2093                                         my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND headertag='$jobdb_headertag' AND status='processing'";
2094                                         my $res = $job_db->exec_statement( $sql_statement );
2095                                         foreach my $hit (@{$res}) {
2096                                                 push @drops, "DELETE FROM $job_queue_tn WHERE id=$jobdb_id";
2097                                         }
2098                                 } else {
2099                                         daemon_log("J ERROR: Job without id exists for macaddress $macaddress!", 1);
2100                                 }
2101                         }
2102                 }
2104                 # Commit deletion
2105                 $job_db->exec_statementlist(\@drops);
2107                 # Look for new jobs that could be executed
2108                 foreach my $macaddress (keys %{$hits}) {
2110                         # Look if there is an executing job
2111                         my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND status='processing'";
2112                         my $res = $job_db->exec_statement( $sql_statement );
2114                         # Skip new jobs for host if there is a processing job
2115                         if(defined($res) and defined @{$res}[0]) {
2116                                 # Prevent race condition if there is a trigger_activate job waiting and a goto-activation job processing
2117                                 my $row = @{$res}[0] if (ref $res eq 'ARRAY');
2118                                 if(@{$row}[5] eq 'trigger_action_reinstall') {
2119                                         my $sql_statement_2 =  "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND status='waiting' AND headertag = 'trigger_activate_new'"; 
2120                                         my $res_2 = $job_db->exec_statement( $sql_statement_2 );
2121                                         if(defined($res_2) and defined @{$res_2}[0]) {
2122                                                 # Set status from goto-activation to 'waiting' and update timestamp
2123                                                 $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'");
2124                                         }
2125                                 }
2126                                 next;
2127                         }
2129                         foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
2130                                 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
2131                                 if(defined($jobdb_id)) {
2132                                         my $job_msg = @{$hits->{$macaddress}->{$jobdb_headertag}}[7];
2134                                         daemon_log("J DEBUG: its time to execute $job_msg", 7);
2135                                         my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$macaddress'";
2136                                         my $res_hash = $known_clients_db->select_dbentry( $sql_statement );
2138                                         # expect macaddress is unique!!!!!!
2139                                         my $target = $res_hash->{1}->{hostname};
2141                                         # change header
2142                                         $job_msg =~ s/<header>job_/<header>gosa_/;
2144                                         # add sqlite_id
2145                                         $job_msg =~ s/<\/xml>$/<jobdb_id>$jobdb_id<\/jobdb_id><\/xml>/;
2147                                         $job_msg =~ /<header>(\S+)<\/header>/;
2148                                         my $header = $1 ;
2149                                         my $func_error = &send_msg_to_target($job_msg, $server_address, $GosaPackages_key, $header, "J");                    
2151                                         # update status in job queue to ...
2152                     # ... 'processing', for jobs: 'reinstall', 'update', activate_new
2153                     if (($header =~ /gosa_trigger_action_reinstall/) 
2154                             || ($header =~ /gosa_trigger_activate_new/)
2155                             || ($header =~ /gosa_trigger_action_update/)) {
2156                         my $sql_statement = "UPDATE $job_queue_tn SET status='processing' WHERE id=$jobdb_id";
2157                         my $dbres = $job_db->update_dbentry($sql_statement);
2158                     }
2160                     # ... 'done', for all other jobs, they are no longer needed in the jobqueue
2161                     else {
2162                         my $sql_statement = "UPDATE $job_queue_tn SET status='done' WHERE id=$jobdb_id";
2163                         my $dbres = $job_db->exec_statement($sql_statement);
2164                     }
2165                 
2167                                         # We don't want parallel processing
2168                                         last;
2169                                 }
2170                         }
2171                 }
2173                 $watch_for_new_jobs_in_progress = 0;
2174                 $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
2175         }
2179 sub watch_for_new_messages {
2180     my ($kernel,$heap) = @_[KERNEL, HEAP];
2181     my @coll_user_msg;   # collection list of outgoing messages
2182     
2183     # check messaging_db for new incoming messages with executable timestamp
2184     my $timestamp = &get_time();
2185     my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( (CAST(timestamp AS UNSIGNED))<$timestamp AND flag='n' AND direction='in' )";
2186     my $res = $messaging_db->exec_statement( $sql_statement );
2187         foreach my $hit (@{$res}) {
2189         # create outgoing messages
2190         my $message_to = @{$hit}[3];
2191         # translate message_to to plain login name
2192         my @message_to_l = split(/,/, $message_to);  
2193                 my %receiver_h; 
2194                 foreach my $receiver (@message_to_l) {
2195                         if ($receiver =~ /^u_([\s\S]*)$/) {
2196                                 $receiver_h{$1} = 0;
2197                         } elsif ($receiver =~ /^g_([\s\S]*)$/) {
2198                                 my $group_name = $1;
2199                                 # fetch all group members from ldap and add them to receiver hash
2200                                 my $ldap_handle = &get_ldap_handle();
2201                                 if (defined $ldap_handle) {
2202                                                 my $mesg = $ldap_handle->search(
2203                                                                                 base => $ldap_base,
2204                                                                                 scope => 'sub',
2205                                                                                 attrs => ['memberUid'],
2206                                                                                 filter => "cn=$group_name",
2207                                                                                 );
2208                                                 if ($mesg->count) {
2209                                                                 my @entries = $mesg->entries;
2210                                                                 foreach my $entry (@entries) {
2211                                                                                 my @receivers= $entry->get_value("memberUid");
2212                                                                                 foreach my $receiver (@receivers) { 
2213                                                                                                 $receiver_h{$receiver} = 0;
2214                                                                                 }
2215                                                                 }
2216                                                 } 
2217                                                 # translating errors ?
2218                                                 if ($mesg->code) {
2219                                                                 daemon_log("M ERROR: unable to translate group '$group_name' to user list for message delivery: $mesg->error", 1);
2220                                                 }
2221                                                 &release_ldap_handle($ldap_handle);
2222                                 # ldap handle error ?           
2223                                 } else {
2224                                         daemon_log("M ERROR: unable to translate group '$group_name' to user list for message delivery: no ldap handle available", 1);
2225                                 }
2226                         } else {
2227                                 my $sbjct = &encode_base64(@{$hit}[1]);
2228                                 my $msg = &encode_base64(@{$hit}[7]);
2229                                 &daemon_log("M WARNING: unknown receiver '$receiver' for a user-message '$sbjct - $msg'", 3); 
2230                         }
2231                 }
2232                 my @receiver_l = keys(%receiver_h);
2234         my $message_id = @{$hit}[0];
2236         #add each outgoing msg to messaging_db
2237         my $receiver;
2238         foreach $receiver (@receiver_l) {
2239             my $sql_statement = "INSERT INTO $messaging_tn (id, subject, message_from, message_to, flag, direction, delivery_time, message, timestamp) ".
2240                 "VALUES ('".
2241                 $message_id."', '".    # id
2242                 @{$hit}[1]."', '".     # subject
2243                 @{$hit}[2]."', '".     # message_from
2244                 $receiver."', '".      # message_to
2245                 "none"."', '".         # flag
2246                 "out"."', '".          # direction
2247                 @{$hit}[6]."', '".     # delivery_time
2248                 @{$hit}[7]."', '".     # message
2249                 $timestamp."'".     # timestamp
2250                 ")";
2251             &daemon_log("M DEBUG: $sql_statement", 1);
2252             my $res = $messaging_db->exec_statement($sql_statement);
2253             &daemon_log("M INFO: message '".@{$hit}[0]."' is prepared for delivery to receiver '$receiver'", 5);
2254         }
2256         # set incoming message to flag d=deliverd
2257         $sql_statement = "UPDATE $messaging_tn SET flag='p' WHERE id='$message_id'"; 
2258         &daemon_log("M DEBUG: $sql_statement", 7);
2259         $res = $messaging_db->update_dbentry($sql_statement);
2260         &daemon_log("M INFO: message '$message_id' is set to flag 'p' (processed)", 5);
2261     }
2263     $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay); 
2264     return;
2267 sub watch_for_delivery_messages {
2268     my ($kernel, $heap) = @_[KERNEL, HEAP];
2270     # select outgoing messages
2271     my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( flag='p' AND direction='out' )";
2272     my $res = $messaging_db->exec_statement( $sql_statement );
2273     
2274     # build out msg for each    usr
2275     foreach my $hit (@{$res}) {
2276         my $receiver = @{$hit}[3];
2277         my $msg_id = @{$hit}[0];
2278         my $subject = @{$hit}[1];
2279         my $message = @{$hit}[7];
2281         # resolve usr -> host where usr is logged in
2282         my $sql = "SELECT * FROM $login_users_tn WHERE (user='$receiver')"; 
2283         my $res = $login_users_db->exec_statement($sql);
2285         # receiver is logged in nowhere
2286         if (not ref(@$res[0]) eq "ARRAY") { next; }    
2288         # receiver ist logged in at a client registered at local server
2289                 my $send_succeed = 0;
2290                 foreach my $hit (@$res) {
2291                                 my $receiver_host = @$hit[0];
2292                 my $delivered2host = 0;
2293                                 &daemon_log("M DEBUG: user '$receiver' is logged in at host '$receiver_host'", 7);
2295                                 # Looking for host in know_clients_db 
2296                                 my $sql = "SELECT * FROM $known_clients_tn WHERE (hostname='$receiver_host')";
2297                                 my $res = $known_clients_db->exec_statement($sql);
2299                 # Host is known in known_clients_db
2300                 if (ref(@$res[0]) eq "ARRAY") {
2301                     my $receiver_key = @{@{$res}[0]}[2];
2302                     my %data = ('subject' => $subject, 'message' => $message, 'usr' => $receiver);
2303                     my $out_msg = &build_msg("usr_msg", $server_address, $receiver_host, \%data ); 
2304                     my $error = &send_msg_to_target($out_msg, $receiver_host, $receiver_key, "usr_msg", 0); 
2305                     if ($error == 0 ) {
2306                         $send_succeed++ ;
2307                         $delivered2host++ ;
2308                         &daemon_log("M DEBUG: send message for user '$receiver' to host '$receiver_host'", 7); 
2309                     } else {
2310                         &daemon_log("M DEBUG: cannot send message for user '$receiver' to host '$receiver_host'", 7); 
2311                     }
2312                 }
2313                 
2314                 # Message already send, do not need to do anything more, otherwise ...
2315                 if ($delivered2host) { next;}
2316     
2317                 # ...looking for host in foreign_clients_db
2318                 $sql = "SELECT * FROM $foreign_clients_tn WHERE (hostname='$receiver_host')";
2319                 $res = $foreign_clients_db->exec_statement($sql);
2320   
2321                                 # Host is known in foreign_clients_db 
2322                                 if (ref(@$res[0]) eq "ARRAY") { 
2323                     my $registration_server = @{@{$res}[0]}[2];
2324                     
2325                     # Fetch encryption key for registration server
2326                     my $sql = "SELECT * FROM $known_server_tn WHERE (hostname='$registration_server')";
2327                     my $res = $known_server_db->exec_statement($sql);
2328                     if (ref(@$res[0]) eq "ARRAY") { 
2329                         my $registration_server_key = @{@{$res}[0]}[3];
2330                         my %data = ('subject' => $subject, 'message' => $message, 'usr' => $receiver);
2331                         my $out_msg = &build_msg("usr_msg", $server_address, $receiver_host, \%data ); 
2332                         my $error = &send_msg_to_target($out_msg, $registration_server, $registration_server_key, "usr_msg", 0); 
2333                         if ($error == 0 ) {
2334                             $send_succeed++ ;
2335                             $delivered2host++ ;
2336                             &daemon_log("M DEBUG: send message for user '$receiver' to server '$registration_server'", 7); 
2337                         } else {
2338                             &daemon_log("M ERROR: cannot send message for user '$receiver' to server '$registration_server'", 1); 
2339                         }
2341                     } else {
2342                         &daemon_log("M ERROR: host '$receiver_host' is reported to be ".
2343                                 "registrated at server '$registration_server', ".
2344                                 "but no data available in known_server_db ", 1); 
2345                     }
2346                 }
2347                 
2348                 if (not $delivered2host) {
2349                     &daemon_log("M ERROR: unable to send user message to host '$receiver_host'", 1);
2350                 }
2351                 }
2353                 if ($send_succeed) {
2354                                 # set outgoing msg at db to deliverd
2355                                 my $sql = "UPDATE $messaging_tn SET flag='d' WHERE (id='$msg_id' AND direction='out' AND message_to='$receiver')"; 
2356                                 my $res = $messaging_db->exec_statement($sql); 
2357                 &daemon_log("M INFO: send message for user '$receiver' to logged in hosts", 5);
2358                 } else {
2359             &daemon_log("M WARNING: failed to deliver message for user '$receiver'", 3); 
2360         }
2361         }
2363     $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay); 
2364     return;
2368 sub watch_for_done_messages {
2369     my ($kernel,$heap) = @_[KERNEL, HEAP];
2371     my $sql = "SELECT * FROM $messaging_tn WHERE (flag='p' AND direction='in')"; 
2372     my $res = $messaging_db->exec_statement($sql); 
2374     foreach my $hit (@{$res}) {
2375         my $msg_id = @{$hit}[0];
2377         my $sql = "SELECT * FROM $messaging_tn WHERE (id='$msg_id' AND direction='out' AND (NOT flag='s'))"; 
2378         my $res = $messaging_db->exec_statement($sql);
2380         # not all usr msgs have been seen till now
2381         if ( ref(@$res[0]) eq "ARRAY") { next; }
2382         
2383         $sql = "DELETE FROM $messaging_tn WHERE (id='$msg_id')"; 
2384         $res = $messaging_db->exec_statement($sql);
2385     
2386     }
2388     $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay); 
2389     return;
2393 sub watch_for_old_known_clients {
2394     my ($kernel,$heap) = @_[KERNEL, HEAP];
2396     my $sql_statement = "SELECT * FROM $known_clients_tn";
2397     my $res = $known_clients_db->select_dbentry( $sql_statement );
2399     my $cur_time = int(&get_time());
2401     while ( my ($hit_num, $hit) = each %$res) {
2402         my $expired_timestamp = int($hit->{'timestamp'});
2403         $expired_timestamp =~ /(\d{4})(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)/;
2404         my $dt = DateTime->new( year   => $1,
2405                 month  => $2,
2406                 day    => $3,
2407                 hour   => $4,
2408                 minute => $5,
2409                 second => $6,
2410                 );
2412         $dt->add( seconds => 2 * int($hit->{'keylifetime'}) );
2413         $expired_timestamp = $dt->ymd('').$dt->hms('');
2414         if ($cur_time > $expired_timestamp) {
2415             my $hostname = $hit->{'hostname'};
2416             my $del_sql = "DELETE FROM $known_clients_tn WHERE hostname='$hostname'"; 
2417             my $del_res = $known_clients_db->exec_statement($del_sql);
2419             &main::daemon_log("0 INFO: timestamp '".$hit->{'timestamp'}."' of client '$hostname' is expired('$expired_timestamp'), client will be deleted from known_clients_db", 5);
2420         }
2422     }
2424     $kernel->delay_set('watch_for_old_known_clients', $job_queue_loop_delay);
2428 sub watch_for_next_tasks {
2429     my ($kernel,$heap) = @_[KERNEL, HEAP];
2431     my $sql = "SELECT * FROM $incoming_tn";
2432     my $res = $incoming_db->select_dbentry($sql);
2433     
2434     while ( my ($hit_num, $hit) = each %$res) {
2435         my $headertag = $hit->{'headertag'};
2436         if ($headertag =~ /^answer_(\d+)/) {
2437             # do not start processing, this message is for a still running POE::Wheel
2438             next;
2439         }
2440         my $message_id = $hit->{'id'};
2441         my $session_id = $hit->{'sessionid'};
2442         &daemon_log("$session_id DEBUG: start processing for message with incoming id: '$message_id'", 11);
2444         $kernel->yield('next_task', $hit);
2446         my $sql = "DELETE FROM $incoming_tn WHERE id=$message_id";
2447         my $res = $incoming_db->exec_statement($sql);
2448     }
2452 sub get_ldap_handle {
2453         my ($session_id) = @_;
2454         my $heap;
2456         if (not defined $session_id ) { $session_id = 0 };
2457         if ($session_id =~ /[^0-9]*/) { $session_id = 0 };
2459         my ($package, $file, $row, $subroutine, $hasArgs, $wantArray, $evalText, $isRequire) = caller(1);
2460         my $caller_text = "subroutine $subroutine";
2461         if ($subroutine eq "(eval)") {
2462                 $caller_text = "eval block within file '$file' for '$evalText'"; 
2463         }
2464         daemon_log("$session_id DEBUG: new ldap handle for '$caller_text' required!", 42);
2466 get_handle:
2467         my $ldap_handle = Net::LDAP->new( $ldap_uri );
2468         if (not ref $ldap_handle) {
2469                 daemon_log("$session_id ERROR: Connection to LDAP URI '$ldap_uri' failed! Retrying in $ldap_retry_sec seconds.", 1); 
2470                 sleep($ldap_retry_sec);
2471                 goto get_handle;
2472         } else {
2473                 daemon_log("$session_id DEBUG: Connection to LDAP URI '$ldap_uri' established.", 42);
2474         }
2476         $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);
2477         return $ldap_handle;
2481 sub release_ldap_handle {
2482         my ($ldap_handle, $session_id) = @_ ;
2483         if (not defined $session_id ) { $session_id = 0 };
2485         if(ref $ldap_handle) {
2486           $ldap_handle->disconnect();
2487   }
2488         &main::daemon_log("$session_id DEBUG: Released a ldap handle!", 42);
2489         return;
2493 sub change_fai_state {
2494         my ($st, $targets, $session_id) = @_;
2495         $session_id = 0 if not defined $session_id;
2496         # Set FAI state to localboot
2497         my %mapActions= (
2498                 reboot    => '',
2499                 update    => 'softupdate',
2500                 localboot => 'localboot',
2501                 reinstall => 'install',
2502                 rescan    => '',
2503                 wake      => '',
2504                 memcheck  => 'memcheck',
2505                 sysinfo   => 'sysinfo',
2506                 install   => 'install',
2507         );
2509         # Return if this is unknown
2510         if (!exists $mapActions{ $st }){
2511                 daemon_log("$session_id ERROR: unknown action '$st', can not translate ot FAIstate", 1); 
2512                 return;
2513         }
2515         my $state= $mapActions{ $st };
2517         # Build search filter for hosts
2518         my $search= "(&(objectClass=GOhard)";
2519         foreach (@{$targets}){
2520                 $search.= "(macAddress=$_)";
2521         }
2522         $search.= ")";
2524         # If there's any host inside of the search string, procress them
2525         if (!($search =~ /macAddress/)){
2526                 daemon_log("$session_id ERROR: no macAddress found in filter statement for LDAP search: '$search'", 1);    
2527                 return;
2528         }
2530         my $ldap_handle = &get_ldap_handle($session_id);
2531         # Perform search for Unit Tag
2532         my $mesg = $ldap_handle->search(
2533                 base   => $ldap_base,
2534                 scope  => 'sub',
2535                 attrs  => ['dn', 'FAIstate', 'objectClass'],
2536                 filter => "$search"
2537         );
2539         if ($mesg->count) {
2540                 my @entries = $mesg->entries;
2541                 if (0 == @entries) {
2542                         daemon_log("$session_id ERROR: ldap search failed: ldap_base=$ldap_base, filter=$search", 1); 
2543                 }
2545                 foreach my $entry (@entries) {
2546                         # Only modify entry if it is not set to '$state'
2547                         if ($entry->get_value("FAIstate") ne "$state"){
2548                                 daemon_log("$session_id INFO: Setting FAIstate to '$state' for ".$entry->dn, 5);
2549                                 my $result;
2550                                 my %tmp = map { $_ => 1 } $entry->get_value("objectClass");
2551                                 if (exists $tmp{'FAIobject'}){
2552                                         if ($state eq ''){
2553                                                 $result= $ldap_handle->modify($entry->dn, changes => [ delete => [ FAIstate => [] ] ]);
2554                                         } else {
2555                                                 $result= $ldap_handle->modify($entry->dn, changes => [ replace => [ FAIstate => $state ] ]);
2556                                         }
2557                                 } elsif ($state ne ''){
2558                                         $result= $ldap_handle->modify($entry->dn, changes => [ add => [ objectClass => 'FAIobject' ], add => [ FAIstate => $state ] ]);
2559                                 }
2561                                 # Errors?
2562                                 if ($result->code){
2563                                         daemon_log("$session_id Error: Setting FAIstate to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
2564                                 }
2565                         } else {
2566                                 daemon_log("$session_id DEBUG FAIstate at host '".$entry->dn."' already at state '$st'", 42); 
2567                         }  
2568                 }
2569         } else {
2570                 daemon_log("$session_id ERROR: LDAP search failed: ldap_base=$ldap_base, filter=$search", 1);
2571         }
2572         &release_ldap_handle($ldap_handle, $session_id);                  
2574         return;
2578 sub change_goto_state {
2579     my ($st, $targets, $session_id) = @_;
2580     $session_id = 0  if not defined $session_id;
2582     # Switch on or off?
2583     my $state= $st eq 'active' ? 'active': 'locked';
2585     my $ldap_handle = &get_ldap_handle($session_id);
2586     if( defined($ldap_handle) ) {
2588       # Build search filter for hosts
2589       my $search= "(&(objectClass=GOhard)";
2590       foreach (@{$targets}){
2591         $search.= "(macAddress=$_)";
2592       }
2593       $search.= ")";
2595       # If there's any host inside of the search string, procress them
2596       if (!($search =~ /macAddress/)){
2597               &release_ldap_handle($ldap_handle);
2598         return;
2599       }
2601       # Perform search for Unit Tag
2602       my $mesg = $ldap_handle->search(
2603           base   => $ldap_base,
2604           scope  => 'sub',
2605           attrs  => ['dn', 'gotoMode'],
2606           filter => "$search"
2607           );
2609       if ($mesg->count) {
2610         my @entries = $mesg->entries;
2611         foreach my $entry (@entries) {
2613           # Only modify entry if it is not set to '$state'
2614           if ($entry->get_value("gotoMode") ne $state){
2616             daemon_log("$session_id INFO: Setting gotoMode to '$state' for ".$entry->dn, 5);
2617             my $result;
2618             $result= $ldap_handle->modify($entry->dn, changes => [replace => [ gotoMode => $state ] ]);
2620             # Errors?
2621             if ($result->code){
2622               &daemon_log("$session_id Error: Setting gotoMode to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
2623             }
2625           }
2626         }
2627       } else {
2628                 daemon_log("$session_id ERROR: LDAP search failed in function change_goto_state: ldap_base=$ldap_base, filter=$search", 1);
2629           }
2631     }
2632         &release_ldap_handle($ldap_handle, $session_id);
2633         return;
2637 sub run_recreate_packages_db {
2638     my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
2639     my $session_id = $session->ID;
2640         &main::daemon_log("$session_id INFO: Recreating FAI Packages DB ('$fai_release_tn', '$fai_server_tn', '$packages_list_tn')", 5);
2641         $kernel->yield('create_fai_release_db', $fai_release_tn);
2642         $kernel->yield('create_fai_server_db', $fai_server_tn);
2643         return;
2647 sub run_create_fai_server_db {
2648     my ($kernel, $session, $heap, $table_name) = @_[KERNEL, SESSION, HEAP, ARG0];
2649     my $session_id = $session->ID;
2650     my $task = POE::Wheel::Run->new(
2651             Program => sub { &create_fai_server_db($table_name,$kernel, undef, $session_id) },
2652             StdoutEvent  => "session_run_result",
2653             StderrEvent  => "session_run_debug",
2654             CloseEvent   => "session_run_done",
2655             );
2657     $heap->{task}->{ $task->ID } = $task;
2658     return;
2662 sub create_fai_server_db {
2663         my ($table_name, $kernel, $dont_create_packages_list, $session_id) = @_;
2664         my $result;
2666         if (not defined $session_id) { $session_id = 0; }
2667         my $ldap_handle = &get_ldap_handle($session_id);
2668         if(defined($ldap_handle)) {
2669                 daemon_log("$session_id INFO: create_fai_server_db: start", 5);
2670                 my $mesg= $ldap_handle->search(
2671                         base   => $ldap_base,
2672                         scope  => 'sub',
2673                         attrs  => ['FAIrepository', 'gosaUnitTag'],
2674                         filter => "(&(FAIrepository=*)(objectClass=FAIrepositoryServer))",
2675                 );
2676                 if($mesg->{'resultCode'} == 0 &&
2677                         $mesg->count != 0) {
2678                         foreach my $entry (@{$mesg->{entries}}) {
2679                                 if($entry->exists('FAIrepository')) {
2680                                         # Add an entry for each Repository configured for server
2681                                         foreach my $repo(@{$entry->get_value('FAIrepository', asref => 1)}) {
2682                                                 my($tmp_url,$tmp_server,$tmp_release,$tmp_sections) = split(/\|/, $repo);
2683                                                 my $tmp_tag= $entry->get_value('gosaUnitTag') || "";
2684                                                 $result= $fai_server_db->add_dbentry( { 
2685                                                                 table => $table_name,
2686                                                                 primkey => ['server', 'fai_release', 'tag'],
2687                                                                 server => $tmp_url,
2688                                                                 fai_release => $tmp_release,
2689                                                                 sections => $tmp_sections,
2690                                                                 tag => (length($tmp_tag)>0)?$tmp_tag:"",
2691                                                         } );
2692                                         }
2693                                 }
2694                         }
2695                 }
2696                 daemon_log("$session_id INFO: create_fai_server_db: finished", 5);
2697                 &release_ldap_handle($ldap_handle);
2699                 # TODO: Find a way to post the 'create_packages_list_db' event
2700                 if(not defined($dont_create_packages_list)) {
2701                         &create_packages_list_db(undef, $session_id);
2702                 }
2703         }       
2705         return $result;
2709 sub run_create_fai_release_db {
2710         my ($kernel, $session, $heap, $table_name) = @_[KERNEL, SESSION, HEAP, ARG0];
2711         my $session_id = $session->ID;
2712         my $task = POE::Wheel::Run->new(
2713                 Program => sub { &create_fai_release_db($table_name, $session_id) },
2714                 StdoutEvent  => "session_run_result",
2715                 StderrEvent  => "session_run_debug",
2716                 CloseEvent   => "session_run_done",
2717         );
2719         $heap->{task}->{ $task->ID } = $task;
2720         return;
2724 sub create_fai_release_db {
2725         my ($table_name, $session_id) = @_;
2726         my $result;
2728         # used for logging
2729         if (not defined $session_id) { $session_id = 0; }
2731         my $ldap_handle = &get_ldap_handle($session_id);
2732         if(defined($ldap_handle)) {
2733                 daemon_log("$session_id INFO: create_fai_release_db: start",5);
2734                 my $mesg= $ldap_handle->search(
2735                         base   => $ldap_base,
2736                         scope  => 'sub',
2737                         attrs  => [],
2738                         filter => "(&(objectClass=organizationalUnit)(ou=fai))",
2739                 );
2740                 if(($mesg->code == 0) && ($mesg->count != 0))
2741                 {
2742                         daemon_log("$session_id DEBUG: create_fai_release_db: count " . $mesg->count,138);
2744                         # Walk through all possible FAI container ou's
2745                         my @sql_list;
2746                         my $timestamp= &get_time();
2747                         foreach my $ou (@{$mesg->{entries}}) {
2748                                 my $tmp_classes= resolve_fai_classes($ou->dn, $ldap_handle, $session_id);
2749                                 if(defined($tmp_classes) && ref($tmp_classes) eq 'HASH') {
2750                                         my @tmp_array=get_fai_release_entries($tmp_classes);
2751                                         if(@tmp_array) {
2752                                                 foreach my $entry (@tmp_array) {
2753                                                         if(defined($entry) && ref($entry) eq 'HASH') {
2754                                                                 my $sql= 
2755                                                                 "INSERT INTO $table_name "
2756                                                                 ."(timestamp, fai_release, class, type, state) VALUES ("
2757                                                                 .$timestamp.","
2758                                                                 ."'".$entry->{'release'}."',"
2759                                                                 ."'".$entry->{'class'}."',"
2760                                                                 ."'".$entry->{'type'}."',"
2761                                                                 ."'".$entry->{'state'}."')";
2762                                                                 push @sql_list, $sql;
2763                                                         }
2764                                                 }
2765                                         }
2766                                 }
2767                         }
2769                         daemon_log("$session_id DEBUG: create_fai_release_db: Inserting ".scalar @sql_list." entries to DB",138);
2770             &release_ldap_handle($ldap_handle);
2771                         if(@sql_list) {
2772                                 unshift @sql_list, "VACUUM";
2773                                 unshift @sql_list, "DELETE FROM $table_name";
2774                                 $fai_release_db->exec_statementlist(\@sql_list);
2775                         }
2776                         daemon_log("$session_id DEBUG: create_fai_release_db: Done with inserting",138);
2777                 } else {
2778                         daemon_log("$session_id INFO: create_fai_release_db: error: " . $mesg->code, 5);
2779                 }
2780                 daemon_log("$session_id INFO: create_fai_release_db: finished",5);
2781         }
2782         return $result;
2785 sub get_fai_types {
2786         my $tmp_classes = shift || return undef;
2787         my @result;
2789         foreach my $type(keys %{$tmp_classes}) {
2790                 if(defined($tmp_classes->{$type}[0]) && (!($tmp_classes->{$type}[0] =~ /^.*?removed.*?$/))) {
2791                         my $entry = {
2792                                 type => $type,
2793                                 state => $tmp_classes->{$type}[0],
2794                         };
2795                         push @result, $entry;
2796                 }
2797         }
2799         return @result;
2802 sub get_fai_state {
2803         my $result = "";
2804         my $tmp_classes = shift || return $result;
2806         foreach my $type(keys %{$tmp_classes}) {
2807                 if(defined($tmp_classes->{$type}[0])) {
2808                         $result = $tmp_classes->{$type}[0];
2809                         
2810                 # State is equal for all types in class
2811                         last;
2812                 }
2813         }
2815         return $result;
2818 sub resolve_fai_classes {
2819         my ($fai_base, $ldap_handle, $session_id) = @_;
2820         if (not defined $session_id) { $session_id = 0; }
2821         my $result;
2822         my @possible_fai_classes= ("FAIscript", "FAIhook", "FAIpartitionTable", "FAItemplate", "FAIvariable", "FAIprofile", "FAIpackageList");
2823         my $fai_filter= "(|(&(objectClass=FAIclass)(|(objectClass=".join(")(objectClass=", @possible_fai_classes).")))(objectClass=FAIbranch))";
2824         my $fai_classes;
2826         daemon_log("$session_id DEBUG: Searching for FAI entries in base $fai_base", 138);
2827         my $mesg= $ldap_handle->search(
2828                 base   => $fai_base,
2829                 scope  => 'sub',
2830                 attrs  => ['cn','objectClass','FAIstate'],
2831                 filter => $fai_filter,
2832         );
2833         daemon_log("$session_id DEBUG: Found ".$mesg->count()." FAI entries", 138);
2835         if($mesg->{'resultCode'} == 0 &&
2836                 $mesg->count != 0) {
2837                 foreach my $entry (@{$mesg->{entries}}) {
2838                         if($entry->exists('cn')) {
2839                                 my $tmp_dn= $entry->dn();
2840                                 $tmp_dn= substr( $tmp_dn, 0, length($tmp_dn)
2841                                         - length($fai_base) - 1 );
2843                                 # Skip classname and ou dn parts for class
2844                                 my $tmp_release = ($1) if $tmp_dn =~ /^[^,]+,[^,]+,(.*?)$/;
2846                                 # Skip classes without releases
2847                                 if((!defined($tmp_release)) || length($tmp_release)==0) {
2848                                         next;
2849                                 }
2851                                 my $tmp_cn= $entry->get_value('cn');
2852                                 my $tmp_state= $entry->get_value('FAIstate');
2854                                 my $tmp_type;
2855                                 # Get FAI type
2856                                 for my $oclass(@{$entry->get_value('objectClass', asref => 1)}) {
2857                                         if(grep $_ eq $oclass, @possible_fai_classes) {
2858                                                 $tmp_type= $oclass;
2859                                                 last;
2860                                         }
2861                                 }
2863                                 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
2864                                         # A Subrelease
2865                                         my @sub_releases = split(/,/, $tmp_release);
2867                                         # Walk through subreleases and build hash tree
2868                                         my $hash;
2869                                         while(my $tmp_sub_release = pop @sub_releases) {
2870                                                 $hash .= "\{'$tmp_sub_release'\}->";                                            
2871                                         }
2872                                         eval('push @{$fai_classes->'.$hash.'{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";');
2873                                 } else {
2874                                         # A branch, no subrelease
2875                                         push @{$fai_classes->{$tmp_release}->{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";
2876                                 }
2877                         } elsif (!$entry->exists('cn')) {
2878                                 my $tmp_dn= $entry->dn();
2879                                 $tmp_dn= substr( $tmp_dn, 0, length($tmp_dn)
2880                                         - length($fai_base) - 1 );
2881                                 my $tmp_release = ($1) if $tmp_dn =~ /^(.*?)$/;
2883                                 # Skip classes without releases
2884                                 if((!defined($tmp_release)) || length($tmp_release)==0) {
2885                                         next;
2886                                 }
2888                                 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
2889                                         # A Subrelease
2890                                         my @sub_releases= split(/,/, $tmp_release);
2892                                         # Walk through subreleases and build hash tree
2893                                         my $hash;
2894                                         while(my $tmp_sub_release = pop @sub_releases) {
2895                                                 $hash .= "\{'$tmp_sub_release'\}->";                                            
2896                                         }
2897                                         # Remove the last two characters
2898                                         chop($hash);
2899                                         chop($hash);
2901                                         eval('$fai_classes->'.$hash.'= {}');
2902                                 } else {
2903                                         # A branch, no subrelease
2904                                         if(!exists($fai_classes->{$tmp_release})) {
2905                                                 $fai_classes->{$tmp_release} = {};
2906                                         }
2907                                 }
2908                         }
2909                 }
2911                 # The hash is complete, now we can honor the copy-on-write based missing entries
2912                 foreach my $release (keys %$fai_classes) {
2913                         $result->{$release}= deep_copy(apply_fai_inheritance($fai_classes->{$release}));
2914                 }
2915         }
2916         return $result;
2919 sub apply_fai_inheritance {
2920        my $fai_classes = shift || return {};
2921        my $tmp_classes;
2923        # Get the classes from the branch
2924        foreach my $class (keys %{$fai_classes}) {
2925                # Skip subreleases
2926                if($class =~ /^ou=.*$/) {
2927                        next;
2928                } else {
2929                        $tmp_classes->{$class}= deep_copy($fai_classes->{$class});
2930                }
2931        }
2933        # Apply to each subrelease
2934        foreach my $subrelease (keys %{$fai_classes}) {
2935                if($subrelease =~ /ou=/) {
2936                        foreach my $tmp_class (keys %{$tmp_classes}) {
2937                                if(!exists($fai_classes->{$subrelease}->{$tmp_class})) {
2938                                        $fai_classes->{$subrelease}->{$tmp_class} =
2939                                        deep_copy($tmp_classes->{$tmp_class});
2940                                } else {
2941                                        foreach my $type (keys %{$tmp_classes->{$tmp_class}}) {
2942                                                if(!exists($fai_classes->{$subrelease}->{$tmp_class}->{$type})) {
2943                                                        $fai_classes->{$subrelease}->{$tmp_class}->{$type}=
2944                                                        deep_copy($tmp_classes->{$tmp_class}->{$type});
2945                                                }
2946                                        }
2947                                }
2948                        }
2949                }
2950        }
2952        # Find subreleases in deeper levels
2953        foreach my $subrelease (keys %{$fai_classes}) {
2954                if($subrelease =~ /ou=/) {
2955                        foreach my $subsubrelease (keys %{$fai_classes->{$subrelease}}) {
2956                                if($subsubrelease =~ /ou=/) {
2957                                        apply_fai_inheritance($fai_classes->{$subrelease});
2958                                }
2959                        }
2960                }
2961        }
2963        return $fai_classes;
2966 sub get_fai_release_entries {
2967         my $tmp_classes = shift || return;
2968         my $parent = shift || "";
2969         my @result = shift || ();
2971         foreach my $entry (keys %{$tmp_classes}) {
2972                 if(defined($entry)) {
2973                         if($entry =~ /^ou=.*$/) {
2974                                 my $release_name = $entry;
2975                                 $release_name =~ s/ou=//g;
2976                                 if(length($parent)>0) {
2977                                         $release_name = $parent."/".$release_name;
2978                                 }
2979                                 my @bufentries = get_fai_release_entries($tmp_classes->{$entry}, $release_name, @result);
2980                                 foreach my $bufentry(@bufentries) {
2981                                         push @result, $bufentry;
2982                                 }
2983                         } else {
2984                                 my @types = get_fai_types($tmp_classes->{$entry});
2985                                 foreach my $type (@types) {
2986                                         push @result, 
2987                                         {
2988                                                 'class' => $entry,
2989                                                 'type' => $type->{'type'},
2990                                                 'release' => $parent,
2991                                                 'state' => $type->{'state'},
2992                                         };
2993                                 }
2994                         }
2995                 }
2996         }
2998         return @result;
3001 sub deep_copy {
3002         my $this = shift;
3003         if (not ref $this) {
3004                 $this;
3005         } elsif (ref $this eq "ARRAY") {
3006                 [map deep_copy($_), @$this];
3007         } elsif (ref $this eq "HASH") {
3008                 +{map { $_ => deep_copy($this->{$_}) } keys %$this};
3009         } else { die "what type is $_?" }
3013 sub session_run_result {
3014     my ($kernel, $heap, $client_answer) = @_[KERNEL, HEAP, ARG0];    
3015     $kernel->sig(CHLD => "child_reap");
3018 sub session_run_debug {
3019     my $result = $_[ARG0];
3020     print STDERR "$result\n";
3023 sub session_run_done {
3024     my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
3025     delete $heap->{task}->{$task_id};
3026         if (exists $heap->{ldap_handle}->{$task_id}) {
3027                 &release_ldap_handle($heap->{ldap_handle}->{$task_id});
3028         }
3029         delete $heap->{ldap_handle}->{$task_id};
3033 sub create_sources_list {
3034         my $session_id = shift || 0;
3035         my $result="/tmp/gosa_si_tmp_sources_list";
3037         # Remove old file
3038         if(stat($result)) {
3039                 unlink($result);
3040                 &main::daemon_log("$session_id DEBUG: remove an old version of '$result'", 7); 
3041         }
3043         open(my $fh, ">", "$result");
3044         if (not defined $fh) {
3045                 &main::daemon_log("$session_id DEBUG: cannot open '$result' for writing", 7); 
3046                 return undef;
3047         }
3048         if(defined($main::ldap_server_dn) and length($main::ldap_server_dn) > 0) {
3049                 my $ldap_handle = &get_ldap_handle($session_id);
3050                 my $mesg=$ldap_handle->search(
3051                         base    => $main::ldap_server_dn,
3052                         scope   => 'base',
3053                         attrs   => 'FAIrepository',
3054                         filter  => 'objectClass=FAIrepositoryServer'
3055                 );
3056                 if($mesg->count) {
3057                         foreach my $entry(@{$mesg->{'entries'}}) {
3058                                 foreach my $value(@{$entry->get_value('FAIrepository', asref => 1)}) {
3059                                         my ($server, $tag, $release, $sections)= split /\|/, $value;
3060                                         my $line = "deb $server $release";
3061                                         $sections =~ s/,/ /g;
3062                                         $line.= " $sections";
3063                                         print $fh $line."\n";
3064                                 }
3065                         }
3066                 }
3067                 &release_ldap_handle($ldap_handle);
3068         } else {
3069                 if (defined $main::ldap_server_dn){
3070                         &main::daemon_log("$session_id ERROR: something wrong with ldap_server_dn '$main::ldap_server_dn', abort create_sources_list", 1); 
3071                 } else {
3072                         &main::daemon_log("$session_id ERROR: no ldap_server_dn found, abort create_sources_list", 1);
3073                 }
3074         }
3075         close($fh);
3077         return $result;
3081 sub run_create_packages_list_db {
3082     my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
3083         my $session_id = $session->ID;
3084         my $task = POE::Wheel::Run->new(
3085                                         Priority => +20,
3086                                         Program => sub {&create_packages_list_db(undef, $session_id)},
3087                                         StdoutEvent  => "session_run_result",
3088                                         StderrEvent  => "session_run_debug",
3089                                         CloseEvent   => "session_run_done",
3090                                         );
3091         $heap->{task}->{ $task->ID } = $task;
3095 sub create_packages_list_db {
3096         my ($sources_file, $session_id) = @_;
3097         
3098         # it should not be possible to trigger a recreation of packages_list_db
3099         # while packages_list_db is under construction, so set flag packages_list_under_construction
3100         # which is tested befor recreation can be started
3101         if (-r $packages_list_under_construction) {
3102                 daemon_log("$session_id WARNING: packages_list_db is right now under construction, please wait until this process is finished", 3);
3103                 return;
3104         } else {
3105                 daemon_log("$session_id INFO: create_packages_list_db: start", 5); 
3106                 # set packages_list_under_construction to true
3107                 system("touch $packages_list_under_construction");
3108                 @packages_list_statements=();
3109         }
3111         if (not defined $session_id) { $session_id = 0; }
3113         if (not defined $sources_file) { 
3114                 &main::daemon_log("$session_id INFO: no sources_file given for creating packages list so trigger creation of it", 5); 
3115                 $sources_file = &create_sources_list($session_id);
3116         }
3118         if (not defined $sources_file) {
3119                 &main::daemon_log("$session_id ERROR: no sources_file given under '$sources_file', skip create_packages_list_db", 1); 
3120                 unlink($packages_list_under_construction);
3121                 return;
3122         }
3124         my $line;
3126         open(my $CONFIG, "<", "$sources_file") or do {
3127                 daemon_log( "$session_id ERROR: create_packages_list_db: Failed to open '$sources_file'", 1);
3128                 unlink($packages_list_under_construction);
3129                 return;
3130         };
3132         # Read lines
3133         while ($line = <$CONFIG>){
3134                 # Unify
3135                 chop($line);
3136                 $line =~ s/^\s+//;
3137                 $line =~ s/^\s+/ /;
3139                 # Strip comments
3140                 $line =~ s/#.*$//g;
3142                 # Skip empty lines
3143                 if ($line =~ /^\s*$/){
3144                         next;
3145                 }
3147                 # Interpret deb line
3148                 if ($line =~ /^deb [^\s]+\s[^\s]+\s[^\s]+/){
3149                         my( $baseurl, $dist, $sections ) = ($line =~ /^deb\s([^\s]+)\s+([^\s]+)\s+(.*)$/);
3150                         my $section;
3151                         foreach $section (split(' ', $sections)){
3152                                 &parse_package_info( $baseurl, $dist, $section, $session_id );
3153                         }
3154                 }
3155     else {
3156         daemon_log("$session_id ERROR: cannot parse line '$line'", 1);
3157     }
3158         }
3160         close ($CONFIG);
3162         if(keys(%repo_dirs)) {
3163                 find(\&cleanup_and_extract, keys( %repo_dirs ));
3164                 &main::strip_packages_list_statements();
3165                 $packages_list_db->exec_statementlist(\@packages_list_statements);
3166         }
3167         unlink($packages_list_under_construction);
3168         daemon_log("$session_id INFO: create_packages_list_db: finished", 5); 
3169         return;
3172 # This function should do some intensive task to minimize the db-traffic
3173 sub strip_packages_list_statements {
3174         my @existing_entries= @{$packages_list_db->exec_statement("SELECT * FROM $main::packages_list_tn")};
3175         my @new_statement_list=();
3176         my $hash;
3177         my $insert_hash;
3178         my $update_hash;
3179         my $delete_hash;
3180         my $known_packages_hash;
3181         my $local_timestamp=get_time();
3183         foreach my $existing_entry (@existing_entries) {
3184                 $hash->{@{$existing_entry}[0]}->{@{$existing_entry}[1]}->{@{$existing_entry}[2]}= $existing_entry;
3185         }
3187         foreach my $statement (@packages_list_statements) {
3188                 if($statement =~ /^INSERT/i) {
3189                         # Assign the values from the insert statement
3190                         my ($distribution,$package,$version,$section,$description,$template,$timestamp) = ($1,$2,$3,$4,$5,$6,$7) if $statement =~ 
3191                         /^INSERT\s+?INTO\s+?$main::packages_list_tn\s+?VALUES\s*?\('(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)'\s*?\)$/si;
3192                         if(exists($hash->{$distribution}->{$package}->{$version})) {
3193                                 # If section or description has changed, update the DB
3194                                 if( 
3195                                         (! (@{$hash->{$distribution}->{$package}->{$version}}[3] eq $section)) or 
3196                                         (! (@{$hash->{$distribution}->{$package}->{$version}}[4] eq $description))
3197                                 ) {
3198                                         @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,undef);
3199                                 } else {
3200                                         # package is already present in database. cache this knowledge for later use
3201                                         @{$known_packages_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
3202                                 }
3203                         } else {
3204                                 # Insert a non-existing entry to db
3205                                 @{$insert_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
3206                         }
3207                 } elsif ($statement =~ /^UPDATE/i) {
3208                         my ($template,$package,$version) = ($1,$2,$3) if $statement =~
3209                         /^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;
3210                         foreach my $distribution (keys %{$hash}) {
3211                                 if(exists($insert_hash->{$distribution}->{$package}->{$version})) {
3212                                         # update the insertion hash to execute only one query per package (insert instead insert+update)
3213                                         @{$insert_hash->{$distribution}->{$package}->{$version}}[5]= $template;
3214                                 } elsif(exists($hash->{$distribution}->{$package}->{$version})) {
3215                                         if( ! (@{$hash->{$distribution}->{$package}->{$version}}[5] eq $template)) {
3216                                                 my $section;
3217                                                 my $description;
3218                                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) and
3219                                                         length(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) > 0 ) {
3220                                                         $section= @{$update_hash->{$distribution}->{$package}->{$version}}[3];
3221                                                 }
3222                                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
3223                                                         $description= @{$update_hash->{$distribution}->{$package}->{$version}}[4];
3224                                                 }
3225                                                 @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
3226                                         }
3227                                 }
3228                         }
3229                 }
3230         }
3232         # Check for orphaned entries
3233         foreach my $existing_entry (@existing_entries) {
3234                 my $distribution= @{$existing_entry}[0];
3235                 my $package= @{$existing_entry}[1];
3236                 my $version= @{$existing_entry}[2];
3237                 my $section= @{$existing_entry}[3];
3239                 if(
3240                         exists($insert_hash->{$distribution}->{$package}->{$version}) ||
3241                         exists($update_hash->{$distribution}->{$package}->{$version}) ||
3242                         exists($known_packages_hash->{$distribution}->{$package}->{$version})
3243                 ) {
3244                         next;
3245                 } else {
3246                         # Insert entry to delete hash
3247                         @{$delete_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section);
3248                 }
3249         }
3251         # unroll the insert hash
3252         foreach my $distribution (keys %{$insert_hash}) {
3253                 foreach my $package (keys %{$insert_hash->{$distribution}}) {
3254                         foreach my $version (keys %{$insert_hash->{$distribution}->{$package}}) {
3255                                 push @new_statement_list, "INSERT INTO $main::packages_list_tn VALUES ('$distribution','$package','$version',"
3256                                 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[3]',"
3257                                 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[4]',"
3258                                 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[5]',"
3259                                 ."'$local_timestamp')";
3260                         }
3261                 }
3262         }
3264         # unroll the update hash
3265         foreach my $distribution (keys %{$update_hash}) {
3266                 foreach my $package (keys %{$update_hash->{$distribution}}) {
3267                         foreach my $version (keys %{$update_hash->{$distribution}->{$package}}) {
3268                                 my $set = "";
3269                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3])) {
3270                                         $set .= "section = '@{$update_hash->{$distribution}->{$package}->{$version}}[3]', ";
3271                                 }
3272                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
3273                                         $set .= "description = '@{$update_hash->{$distribution}->{$package}->{$version}}[4]', ";
3274                                 }
3275                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[5])) {
3276                                         $set .= "template = '@{$update_hash->{$distribution}->{$package}->{$version}}[5]', ";
3277                                 }
3278                                 if(defined($set) and length($set) > 0) {
3279                                         $set .= "timestamp = '$local_timestamp'";
3280                                 } else {
3281                                         next;
3282                                 }
3283                                 push @new_statement_list, 
3284                                 "UPDATE $main::packages_list_tn SET $set WHERE"
3285                                 ." distribution = '$distribution'"
3286                                 ." AND package = '$package'"
3287                                 ." AND version = '$version'";
3288                         }
3289                 }
3290         }
3291         
3292         # unroll the delete hash
3293         foreach my $distribution (keys %{$delete_hash}) {
3294                 foreach my $package (keys %{$delete_hash->{$distribution}}) {
3295                         foreach my $version (keys %{$delete_hash->{$distribution}->{$package}}) {
3296                                 my $section = @{$delete_hash->{$distribution}->{$package}->{$version}}[3];
3297                                 push @new_statement_list, "DELETE FROM $main::packages_list_tn WHERE distribution='$distribution' AND package='$package' AND version='$version' AND section='$section'";
3298                         }
3299                 }
3300         }
3302         unshift(@new_statement_list, "VACUUM");
3304         @packages_list_statements = @new_statement_list;
3308 sub parse_package_info {
3309     my ($baseurl, $dist, $section, $session_id)= @_;
3310     my ($package);
3311     if (not defined $session_id) { $session_id = 0; }
3312     my ($path) = ($baseurl =~ m%://[^/]*(.*)$%);
3313     $repo_dirs{ "${repo_path}/pool" } = 1;
3315     foreach $package ("Packages.gz"){
3316         daemon_log("$session_id DEBUG: create_packages_list: fetch $baseurl, $dist, $section", 266);
3317         get_package( "$baseurl/dists/$dist/$section/binary-$arch/$package", "$outdir/$dist/$section", $session_id );
3318         parse_package( "$outdir/$dist/$section", $dist, $path, $session_id );
3319     }
3320     
3324 sub get_package {
3325     my ($url, $dest, $session_id)= @_;
3326     if (not defined $session_id) { $session_id = 0; }
3328     my $tpath = dirname($dest);
3329     -d "$tpath" || mkpath "$tpath";
3331     # This is ugly, but I've no time to take a look at "how it works in perl"
3332     if(0 == system("wget '$url' -O '$dest' 2>/dev/null") ) {
3333         system("gunzip -cd '$dest' > '$dest.in'");
3334         daemon_log("$session_id DEBUG: run command: gunzip -cd '$dest' > '$dest.in'", 266);
3335         unlink($dest);
3336         daemon_log("$session_id DEBUG: delete file '$dest'", 266); 
3337     } else {
3338         daemon_log("$session_id ERROR: create_packages_list_db: get_packages: fetching '$url' into '$dest' failed!", 1);
3339     }
3340     return 0;
3344 sub parse_package {
3345     my ($path, $dist, $srv_path, $session_id)= @_;
3346     if (not defined $session_id) { $session_id = 0;}
3347     my ($package, $version, $section, $description);
3348     my $timestamp = &get_time();
3350     if(not stat("$path.in")) {
3351         daemon_log("$session_id ERROR: create_packages_list: parse_package: file '$path.in' is not readable",1);
3352         return;
3353     }
3355     open(my $PACKAGES, "<", "$path.in");
3356     if(not defined($PACKAGES)) {
3357         daemon_log("$session_id ERROR: create_packages_list_db: parse_package: cannot open '$path.in'",1); 
3358         return;
3359     }
3361     # Read lines
3362     while (<$PACKAGES>){
3363         my $line = $_;
3364         # Unify
3365         chop($line);
3367         # Use empty lines as a trigger
3368         if ($line =~ /^\s*$/){
3369             my $sql = "INSERT INTO packages_list VALUES ('$dist', '$package', '$version', '$section', '$description', '', '$timestamp')";
3370             push(@packages_list_statements, $sql);
3371             $package = "none";
3372             $version = "none";
3373             $section = "none";
3374             $description = "none"; 
3375             next;
3376         }
3378         # Trigger for package name
3379         if ($line =~ /^Package:\s/){
3380             ($package)= ($line =~ /^Package: (.*)$/);
3381             next;
3382         }
3384         # Trigger for version
3385         if ($line =~ /^Version:\s/){
3386             ($version)= ($line =~ /^Version: (.*)$/);
3387             next;
3388         }
3390         # Trigger for description
3391         if ($line =~ /^Description:\s/){
3392             ($description)= &encode_base64(($line =~ /^Description: (.*)$/));
3393             next;
3394         }
3396         # Trigger for section
3397         if ($line =~ /^Section:\s/){
3398             ($section)= ($line =~ /^Section: (.*)$/);
3399             next;
3400         }
3402         # Trigger for filename
3403         if ($line =~ /^Filename:\s/){
3404             my ($filename) = ($line =~ /^Filename: (.*)$/);
3405             store_fileinfo( $package, $filename, $dist, $srv_path, $version, $repo_path );
3406             next;
3407         }
3408     }
3410     close( $PACKAGES );
3411     unlink( "$path.in" );
3415 sub store_fileinfo {
3416     my( $package, $file, $dist, $path, $vers, $srvdir) = @_;
3418     my %fileinfo = (
3419         'package' => $package,
3420         'dist' => $dist,
3421         'version' => $vers,
3422     );
3424     $repo_files{ "${srvdir}/$file" } = \%fileinfo;
3428 sub cleanup_and_extract {
3429         my $fileinfo = $repo_files{ $File::Find::name };
3431         if( defined $fileinfo ) {
3432                 my $dir = "$outdir/$fileinfo->{ 'dist' }/debconf.d";
3433                 my $sql;
3434                 my $package = $fileinfo->{ 'package' };
3435                 my $newver = $fileinfo->{ 'version' };
3437                 mkpath($dir);
3438                 system( "dpkg -e '$File::Find::name' '$dir/DEBIAN'" );
3440                 if( -f "$dir/DEBIAN/templates" ) {
3442                         daemon_log("0 DEBUG: Found debconf templates in '$package' - $newver", 266);
3444                         my $tmpl= ""; {
3445                                 local $/=undef;
3446                                 open(my $FILE, "$dir/DEBIAN/templates");
3447                                 $tmpl = &encode_base64(<$FILE>);
3448                                 close($FILE);
3449                         }
3450                         rmtree("$dir/DEBIAN/templates");
3452                         $sql= "update $main::packages_list_tn set template = '$tmpl' where package = '$package' and version = '$newver';";
3453                         push @packages_list_statements, $sql;
3454                 }
3455         }
3457         return;
3461 sub prepare_server_registration 
3463         # Add foreign server from cfg file
3464         my @foreign_server_list;
3465         if ($foreign_server_string ne "") {
3466             my @cfg_foreign_server_list = split(",", $foreign_server_string);
3467             foreach my $foreign_server (@cfg_foreign_server_list) {
3468                 push(@foreign_server_list, $foreign_server);
3469             }
3470         
3471             daemon_log("0 INFO: found foreign server in config file: ".join(", ", @foreign_server_list), 5);
3472         }
3473         
3474         # Perform a DNS lookup for server registration if flag is true
3475         if ($dns_lookup eq "true") {
3476             # Add foreign server from dns
3477             my @tmp_servers;
3478             if (not $server_domain) {
3479                 # Try our DNS Searchlist
3480                 for my $domain(get_dns_domains()) {
3481                     chomp($domain);
3482                     my ($tmp_domains, $error_string) = &get_server_addresses($domain);
3483                     if(@$tmp_domains) {
3484                         for my $tmp_server(@$tmp_domains) {
3485                             push @tmp_servers, $tmp_server;
3486                         }
3487                     }
3488                 }
3489                 if(@tmp_servers && length(@tmp_servers)==0) {
3490                     daemon_log("0 WARNING: no foreign gosa-si-server found in DNS for domain '$server_domain'", 3);
3491                 }
3492             } else {
3493                 @tmp_servers = &get_server_addresses($server_domain);
3494                 if( 0 == @tmp_servers ) {
3495                     daemon_log("0 WARNING: no foreign gosa-si-server found in DNS for domain '$server_domain'", 3);
3496                 }
3497             }
3498         
3499             daemon_log("0 INFO: found foreign server via DNS ".join(", ", @tmp_servers), 5);    
3500         
3501             foreach my $server (@tmp_servers) { 
3502                 unshift(@foreign_server_list, $server); 
3503             }
3504         } else {
3505             daemon_log("0 INFO: DNS lookup for server registration is disabled", 5);
3506         }
3507         
3508         # eliminate duplicate entries
3509         @foreign_server_list = &del_doubles(@foreign_server_list);
3510         my $all_foreign_server = join(", ", @foreign_server_list);
3511         daemon_log("0 INFO: found foreign server in config file and DNS: '$all_foreign_server'", 5);
3512         
3513         # add all found foreign servers to known_server
3514         my $cur_timestamp = &get_time();
3515         foreach my $foreign_server (@foreign_server_list) {
3516         
3517                 # do not add myself to known_server_db
3518                 if (&is_local($foreign_server)) { next; }
3519                 ######################################
3520         
3521             my $res = $known_server_db->add_dbentry( {table=>$known_server_tn, 
3522                     primkey=>['hostname'],
3523                     hostname=>$foreign_server,
3524                     macaddress=>"",
3525                     status=>'not_yet_registered',
3526                     hostkey=>"none",
3527                     loaded_modules => "none", 
3528                     timestamp=>$cur_timestamp,
3529                                 update_time=>'19700101000000',
3530                     } );
3531         }
3534 sub register_at_foreign_servers {   
3535     my ($kernel) = $_[KERNEL];
3537         # Update status and update-time of all si-server with expired update_time and 
3538         # block them for race conditional registration processes of other si-servers.
3539         my $act_time = &get_time();
3540         my $block_statement = "UPDATE $known_server_tn SET status='new_server',update_time='19700101000000' WHERE (CAST(update_time AS UNSIGNED))<$act_time ";
3541         my $block_res = $known_server_db->exec_statement($block_statement);
3543         # Fetch all si-server from db where update_time is younger than act_time
3544         my $fetch_statement = "SELECT * FROM $known_server_tn WHERE update_time='19700101000000'"; 
3545         my $fetch_res = $known_server_db->exec_statement($fetch_statement);
3547     # Detect already connected clients. Will be added to registration msg later. 
3548     my $client_sql = "SELECT * FROM $known_clients_tn"; 
3549     my $client_res = $known_clients_db->exec_statement($client_sql);
3551         # Send registration messag to all fetched si-server
3552     foreach my $hit (@$fetch_res) {
3553         my $hostname = @$hit[0];
3554         my $hostkey = &create_passwd;
3556         # Add already connected clients to registration message 
3557         my $myhash = &create_xml_hash('new_server', $server_address, $hostname);
3558         &add_content2xml_hash($myhash, 'key', $hostkey);
3559         map(&add_content2xml_hash($myhash, 'client', @{$_}[0].",".@{$_}[4]), @$client_res);
3561         # Add locally loaded gosa-si modules to registration message
3562         my $loaded_modules = {};
3563         while (my ($package, $pck_info) = each %$known_modules) {
3564                         next if ((!defined(@$pck_info[2])) || (!(ref (@$pck_info[2]) eq 'HASH')));
3565                         foreach my $act_module (keys(%{@$pck_info[2]})) {
3566                                 $loaded_modules->{$act_module} = ""; 
3567                         }
3568                 }
3569         map(&add_content2xml_hash($myhash, "loaded_modules", $_), keys(%$loaded_modules));
3571         # Add macaddress to registration message
3572         my ($host_ip, $host_port) = split(/:/, $hostname);
3573         my $local_ip = &get_local_ip_for_remote_ip($host_ip);
3574         my $network_interface= &get_interface_for_ip($local_ip);
3575         my $host_mac = &get_mac_for_interface($network_interface);
3576         &add_content2xml_hash($myhash, 'macaddress', $host_mac);
3577         
3578         # Build registration message and send it
3579         my $foreign_server_msg = &create_xml_string($myhash);
3580         my $error = &send_msg_to_target($foreign_server_msg, $hostname, $ServerPackages_key, "new_server", 0); 
3581     }
3584         # After n sec perform a check of all server registration processes
3585     $kernel->delay_set("control_server_registration", 2); 
3587         return;
3591 sub control_server_registration {
3592         my ($kernel) = $_[KERNEL];
3593         
3594         # Check if all registration processes succeed or not
3595         my $select_statement = "SELECT * FROM $known_server_tn WHERE status='new_server'"; 
3596         my $select_res = $known_server_db->exec_statement($select_statement);
3598         # If at least one registration process failed, maybe in case of a race condition
3599         # with a foreign registration process
3600         if (@$select_res > 0) 
3601         {
3602                 # Release block statement 'new_server' to make the server accessible
3603                 # for foreign registration processes
3604                 my $update_statement = "UPDATE $known_server_tn SET status='waiting' WHERE status='new_server'";        
3605                 my $update_res = $known_server_db->exec_statement($update_statement);
3607                 # Set a random delay to avoid the registration race condition
3608                 my $new_foreign_servers_register_delay = int(rand(4))+1;
3609                 $kernel->delay_set("register_at_foreign_servers", $new_foreign_servers_register_delay);
3610         }
3611         # If all registration processes succeed
3612         else
3613         {
3614                 $kernel->delay_set("register_at_foreign_servers", $foreign_servers_register_delay);
3615         }
3617         return;
3621 #==== MAIN = main ==============================================================
3623 # Parse options and allow '-vvv'
3624 Getopt::Long::Configure( 'bundling' );
3625 GetOptions( 'v|verbose+' => \$verbose,
3626             'h|help' => \&usage,
3627             'c|config=s' => \$config,
3628             'x|dump-config=i' => \$dump_config,
3629             'f|foreground' => \$foreground)
3630                                                 'd=s' => \$debug_parts,
3631   or usage( '', 1 );
3633 # We may want to dump the default configuration
3634 if( defined $dump_config ) {
3635   if($dump_config==1) {
3636         } elsif ($dump_config==2) {
3637     dump_configuration( $dump_config ); 
3638         } else {
3639     usage( "Dump configuration value has to be 1 or 2" );
3640         }
3643 #  read and set config parameters
3644 #&check_cmdline_param ;
3645 #&read_configfile($cfg_file, %cfg_defaults);
3646 &read_configfile($config, %cfg_defaults);
3647 #&check_pid;
3649 # not sure but seems to be for igonring phantom
3650 $SIG{CHLD} = 'IGNORE';
3652 # Create the PID object
3653 # Ensure you put a name that won't clobber
3654 #   another program's PID file
3655 my $pid = File::Pid->new({
3656    file  => $pidfile,
3657 });
3659 # Write the PID file
3660 $pid->write;
3662 # forward error messages to logfile
3663 if( ! $foreground ) {
3664   open( STDIN,  '+>/dev/null' );
3665   open( STDOUT, '+>&STDIN'    );
3666   open( STDERR, '+>&STDIN'    );
3669 # Just fork, if we are not in foreground mode
3670 if( ! $foreground ) { 
3671     chdir '/'                 or die "Can't chdir to /: $!";
3672     $pid = fork;
3673     setsid                    or die "Can't start a new session: $!";
3674     umask 0;
3675 #} else {
3676 #    $pid = $$;
3677 #}
3679 # Do something useful - put our PID into the pid_file
3680 #if( 0 != $pid ) {
3681 #    open( my $LOCK_FILE, ">", "$pid_file" );
3682 #    print $LOCK_FILE "$pid\n";
3683 #    close( $LOCK_FILE );
3684 #    if( !$foreground ) {
3685 #        exit( 0 )
3686 #    };
3687 #}
3689 # parse head url and revision from svn
3690 my $server_status_hash = { 'developmental'=>'revision', 'stable'=>'release'};
3691 $server_version =~ /^\$HeadURL: (\S+) \$:\$Rev: (\d+) \$$/;
3692 $server_headURL = defined $1 ? $1 : 'unknown' ;
3693 $server_revision = defined $2 ? $2 : 'unknown' ;
3694 if ($server_headURL =~ /\/tag\// || 
3695         $server_headURL =~ /\/branches\// ) {
3696     $server_status = "stable"; 
3697 } else {
3698     $server_status = "developmental" ;
3700 # Prepare log file and set permissions
3701 $root_uid = getpwnam('root');
3702 $adm_gid = getgrnam('adm');
3703 open(my $FH, ">>", "$log_file");
3704 close($FH);
3705 chmod(0440, $log_file);
3706 chown($root_uid, $adm_gid, $log_file);
3707 chown($root_uid, $adm_gid, "/var/lib/gosa-si");
3709 daemon_log(" ", 1);
3710 daemon_log("$0 started!", 1);
3711 daemon_log("status: $server_status", 1);
3712 daemon_log($server_status_hash->{$server_status}.": $server_revision", 1); 
3714 # Buildup data bases
3716     no strict "refs";
3718     if ($db_module eq "DBmysql") {
3720         daemon_log("0 INFO: importing database module '$db_module'", 1);
3722         # connect to incoming_db
3723         $incoming_db = ("GOsaSI::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3725         # connect to gosa-si job queue
3726         $job_db = ("GOsaSI::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3728         # connect to known_clients_db
3729         $known_clients_db = ("GOsaSI::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3731         # connect to foreign_clients_db
3732         $foreign_clients_db = ("GOsaSI::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3734         # connect to known_server_db
3735         $known_server_db = ("GOsaSI::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3737         # connect to login_usr_db
3738         $login_users_db = ("GOsaSI::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3740         # connect to fai_server_db 
3741         $fai_server_db = ("GOsaSI::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3743         # connect to fai_release_db
3744         $fai_release_db = ("GOsaSI::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3746         # connect to packages_list_db
3747         $packages_list_db = ("GOsaSI::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3749         # connect to messaging_db
3750         $messaging_db = ("GOsaSI::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3752     } elsif ($db_module eq "DBsqlite") {
3754         daemon_log("0 INFO: importing database module '$db_module'", 1);
3756         # connect to incoming_db
3757         unlink($incoming_file_name);
3758         $incoming_db = ("GOsaSI::".$db_module)->new($incoming_file_name);
3759         chmod(0640, $incoming_file_name);
3760         chown($root_uid, $adm_gid, $incoming_file_name);
3761         
3762         # connect to gosa-si job queue
3763         $job_db = ("GOsaSI::".$db_module)->new($job_queue_file_name);
3764         chmod(0640, $job_queue_file_name);
3765         chown($root_uid, $adm_gid, $job_queue_file_name);
3766         
3767         # connect to known_clients_db
3768         #unlink($known_clients_file_name);
3769         $known_clients_db = ("GOsaSI::".$db_module)->new($known_clients_file_name);
3770         chmod(0640, $known_clients_file_name);
3771         chown($root_uid, $adm_gid, $known_clients_file_name);
3772         
3773         # connect to foreign_clients_db
3774         #unlink($foreign_clients_file_name);
3775         $foreign_clients_db = ("GOsaSI::".$db_module)->new($foreign_clients_file_name);
3776         chmod(0640, $foreign_clients_file_name);
3777         chown($root_uid, $adm_gid, $foreign_clients_file_name);
3778         
3779         # connect to known_server_db
3780         unlink($known_server_file_name);   # do not delete, gosa-si-server should be forced to check config file and dns at each start
3781         $known_server_db = ("GOsaSI::".$db_module)->new($known_server_file_name);
3782         chmod(0640, $known_server_file_name);
3783         chown($root_uid, $adm_gid, $known_server_file_name);
3784         
3785         # connect to login_usr_db
3786         #unlink($login_users_file_name);
3787         $login_users_db = ("GOsaSI::".$db_module)->new($login_users_file_name);
3788         chmod(0640, $login_users_file_name);
3789         chown($root_uid, $adm_gid, $login_users_file_name);
3790         
3791         # connect to fai_server_db
3792         unlink($fai_server_file_name);
3793         $fai_server_db = ("GOsaSI::".$db_module)->new($fai_server_file_name);
3794         chmod(0640, $fai_server_file_name);
3795         chown($root_uid, $adm_gid, $fai_server_file_name);
3796         
3797         # connect to fai_release_db
3798         unlink($fai_release_file_name);
3799         $fai_release_db = ("GOsaSI::".$db_module)->new($fai_release_file_name);
3800         chmod(0640, $fai_release_file_name);
3801         chown($root_uid, $adm_gid, $fai_release_file_name);
3802         
3803         # connect to packages_list_db
3804         unlink($packages_list_under_construction);
3805         $packages_list_db = ("GOsaSI::".$db_module)->new($packages_list_file_name);
3806         chmod(0640, $packages_list_file_name);
3807         chown($root_uid, $adm_gid, $packages_list_file_name);
3808         
3809         # connect to messaging_db
3810         #unlink($messaging_file_name);
3811         $messaging_db = ("GOsaSI::".$db_module)->new($messaging_file_name);
3812         chmod(0640, $messaging_file_name);
3813         chown($root_uid, $adm_gid, $messaging_file_name);
3814     }
3817 # Creating tables
3819 daemon_log("0 INFO: creating tables in database with '$db_module'", 1);
3821 $messaging_db->create_table($messaging_tn, \@messaging_col_names);
3822 $packages_list_db->create_table($packages_list_tn, \@packages_list_col_names);
3823 $fai_release_db->create_table($fai_release_tn, \@fai_release_col_names);
3824 $fai_server_db->create_table($fai_server_tn, \@fai_server_col_names);
3825 $login_users_db->create_table($login_users_tn, \@login_users_col_names);
3826 $known_server_db->create_table($known_server_tn, \@known_server_col_names);
3827 $foreign_clients_db->create_table($foreign_clients_tn, \@foreign_clients_col_names);
3828 $known_clients_db->create_table($known_clients_tn, \@known_clients_col_names);
3829 $incoming_db->create_table($incoming_tn, \@incoming_col_names);
3830 $job_db->create_table($job_queue_tn, \@job_queue_col_names);
3832 # create xml object used for en/decrypting
3833 $xml = new XML::Simple();
3835 # Import all modules
3836 &import_modules;
3838 # Check wether all modules are gosa-si valid passwd check
3839 &password_check;
3841 # Check DNS and config file for server registration
3842 if ($serverPackages_enabled eq "true") { &prepare_server_registration; }
3844 # Create functions hash
3845 while (my ($module, @mod_info) = each %$known_modules) 
3847         while (my ($plugin, $functions) = each %{$mod_info[0][2]})
3848         {
3849                 while (my ($function, $nothing) = each %$functions )
3850                 {
3851                         $known_functions->{$function} = $nothing;
3852                 }
3853         }
3856 # Prepare for using Opsi 
3857 if ($opsi_enabled eq "true") {
3858     use JSON::RPC::Client;
3859     use XML::Quote qw(:all);
3860     $opsi_url= "https://".$opsi_admin.":".$opsi_password."@".$opsi_server.":4447/rpc";
3861     $opsi_client = new JSON::RPC::Client;
3865 POE::Component::Server::TCP->new(
3866         Alias => "TCP_SERVER",
3867         Port => $server_port,
3868         ClientInput => sub {
3869                 my ($kernel, $input, $heap, $session) = @_[KERNEL, ARG0, HEAP, SESSION];
3870         my $session_id = $session->ID;
3871                 if ($input =~ /;([\d\.]+):([\d]+)$/) 
3872                 {
3873                         # Messages from other servers should be blocked if config option is set
3874                         if (($2 eq $server_port) && ($serverPackages_enabled eq "false"))
3875                         {
3876                                 return;
3877                         }
3878                         &daemon_log("$session_id DEBUG: incoming message from '$1:$2'", 11);
3879                 }
3880                 else
3881                 {
3882                         my $remote_ip = $heap->{'remote_ip'};
3883                         &daemon_log("$session_id DEBUG: incoming message from '$remote_ip'", 11);
3884                 }
3885                 push(@msgs_to_decrypt, $input);
3886                 $kernel->yield("msg_to_decrypt");
3887         },
3888         InlineStates => {
3889                 msg_to_decrypt => \&msg_to_decrypt,
3890                 watch_for_next_tasks => \&watch_for_next_tasks,
3891                 next_task => \&next_task,
3892                 task_result => \&handle_task_result,
3893                 task_done   => \&handle_task_done,
3894                 task_debug  => \&handle_task_debug,
3895                 child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!"  },
3896         }
3897 );
3899 daemon_log("0 INFO: start socket for incoming xml messages at port '$server_port' ", 1);
3901 # create session for repeatedly checking the job queue for jobs
3902 POE::Session->create(
3903         inline_states => {
3904                 _start => \&session_start,
3905         register_at_foreign_servers => \&register_at_foreign_servers,
3906                 control_server_registration => \&control_server_registration,
3907         sig_handler => \&sig_handler,
3908         next_task => \&next_task,
3909         task_result => \&handle_task_result,
3910         task_done   => \&handle_task_done,
3911         task_debug  => \&handle_task_debug,
3912         watch_for_new_messages => \&watch_for_new_messages,
3913         watch_for_delivery_messages => \&watch_for_delivery_messages,
3914         watch_for_done_messages => \&watch_for_done_messages,
3915                 watch_for_new_jobs => \&watch_for_new_jobs,
3916         watch_for_modified_jobs => \&watch_for_modified_jobs,
3917         watch_for_done_jobs => \&watch_for_done_jobs,
3918         watch_for_opsi_jobs => \&watch_for_opsi_jobs,
3919         watch_for_old_known_clients => \&watch_for_old_known_clients,
3920         create_packages_list_db => \&run_create_packages_list_db,
3921         create_fai_server_db => \&run_create_fai_server_db,
3922         create_fai_release_db => \&run_create_fai_release_db,
3923                 recreate_packages_db => \&run_recreate_packages_db,
3924         session_run_result => \&session_run_result,
3925         session_run_debug => \&session_run_debug,
3926         session_run_done => \&session_run_done,
3927         child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!"  },
3928         }
3929 );
3932 POE::Kernel->run();
3933 exit;