Code

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