Code

e8dbfae9fd7d6d11c8fe316d60122ca2670c3f42
[gosa.git] / gosa-si / gosa-si-server
1 #!/usr/bin/perl
2 #===============================================================================
3 #
4 #         FILE:  gosa-sd
5 #
6 #        USAGE:  ./gosa-sd
7 #
8 #  DESCRIPTION:
9 #
10 #      OPTIONS:  ---
11 # REQUIREMENTS:  libconfig-inifiles-perl libcrypt-rijndael-perl libxml-simple-perl 
12 #                libdata-dumper-simple-perl libdbd-sqlite3-perl libnet-ldap-perl
13 #                libpoe-perl
14 #         BUGS:  ---
15 #        NOTES:
16 #       AUTHOR:   (Andreas Rettenberger), <rettenberger@gonicus.de>
17 #      COMPANY:
18 #      VERSION:  1.0
19 #      CREATED:  12.09.2007 08:54:41 CEST
20 #     REVISION:  ---
21 #===============================================================================
23 my $server_version = '$HeadURL: https://oss.gonicus.de/repositories/gosa/trunk/gosa-si/gosa-si-server $:$Rev$';
25 use strict;
26 use warnings;
27 use Getopt::Long;
28 use Config::IniFiles;
29 use POSIX;
31 use Fcntl qw/:flock/;
32 use IO::Socket::INET;
33 use IO::Handle;
34 use IO::Select;
35 use Symbol qw(qualify_to_ref);
36 use Crypt::Rijndael;
37 use MIME::Base64;
38 use Digest::MD5  qw(md5 md5_hex md5_base64);
39 use XML::Simple;
40 use Data::Dumper;
41 use Sys::Syslog qw( :DEFAULT setlogsock);
42 use Time::HiRes qw( usleep);
43 use Cwd;
44 use File::Spec;
45 use File::Basename;
46 use File::Find;
47 use File::Copy;
48 use File::Path;
49 use GOSA::GosaSupportDaemon;
50 use POE qw(Component::Server::TCP Wheel::Run Filter::Reference);
51 use Net::LDAP;
52 use Net::LDAP::Util qw(:escape);
54 # revision number of server and program name
55 my $server_headURL;
56 my $server_revision;
57 my $server_status;
58 our $prg= basename($0);
59 our $verbose= 0;
61 my $db_module = "DBsqlite";
62 {
63 no strict "refs";
64 require ("GOSA/".$db_module.".pm");
65 ("GOSA/".$db_module)->import;
66 #daemon_log("0 INFO: importing database module '$db_module'", 1);
67 }
69 my $modules_path = "/usr/lib/gosa-si/modules";
70 use lib "/usr/lib/gosa-si/modules";
72 our $global_kernel;
73 my ($foreground, $ping_timeout);
74 my ($server);
75 my ($gosa_server, $job_queue_timeout, $job_queue_loop_delay);
76 my ($messaging_db_loop_delay);
77 my ($procid, $pid);
78 my $arp_fifo;
79 my $debug_parts = 0;
80 my $debug_parts_bitstring;
81 my ($xml);
82 my $sources_list;
83 my $max_clients;
84 my %repo_files=();
85 my $repo_path;
86 my %repo_dirs=();
88 # Variables declared in config file are always set to 'our'
89 our (%cfg_defaults, $log_file, $pid_file, 
90     $server_ip, $server_port, $ClientPackages_key, $dns_lookup,
91     $arp_activ, $gosa_unit_tag,
92     $GosaPackages_key, $gosa_timeout,
93     $foreign_server_string, $server_domain, $ServerPackages_key, $foreign_servers_register_delay,
94     $wake_on_lan_passwd, $job_synchronization, $modified_jobs_loop_delay,
95     $arp_enabled, $arp_interface,
96     $opsi_enabled, $opsi_server, $opsi_admin, $opsi_password,
97                 $new_systems_ou,
98 );
100 # additional variable which should be globaly accessable
101 our $server_address;
102 our $server_mac_address;
103 our $gosa_address;
104 our $no_arp;
105 our $forground;
106 our $cfg_file;
107 our ($ldap_uri, $ldap_base, $ldap_admin_dn, $ldap_admin_password, $ldap_server_dn, $ldap_version);
108 our ($mysql_username, $mysql_password, $mysql_database, $mysql_host);
109 our $known_modules;
110 our $known_functions;
111 our $root_uid;
112 our $adm_gid;
114 # if foreground is not null, script will be not forked to background
115 $foreground = 0 ;
117 # specifies the timeout seconds while checking the online status of a registrating client
118 $ping_timeout = 5;
120 $no_arp = 0;
121 my $packages_list_under_construction = "/tmp/packages_list_creation_in_progress";
122 my @packages_list_statements;
123 my $watch_for_new_jobs_in_progress = 0;
125 # holds all incoming decrypted messages
126 our $incoming_db;
127 our $incoming_tn = 'incoming';
128 my $incoming_file_name;
129 my @incoming_col_names = ("id INTEGER PRIMARY KEY",
130         "timestamp VARCHAR(14) DEFAULT 'none'", 
131         "headertag VARCHAR(255) DEFAULT 'none'",
132         "targettag VARCHAR(255) DEFAULT 'none'",
133         "xmlmessage TEXT",
134         "module VARCHAR(255) DEFAULT 'none'",
135         "sessionid VARCHAR(255) DEFAULT '0'",
136 );
138 # holds all gosa jobs
139 our $job_db;
140 our $job_queue_tn = 'jobs';
141 my $job_queue_file_name;
142 my @job_queue_col_names = ("id INTEGER PRIMARY KEY",
143         "timestamp VARCHAR(14) DEFAULT 'none'", 
144         "status VARCHAR(255) DEFAULT 'none'", 
145         "result TEXT",
146         "progress VARCHAR(255) DEFAULT 'none'",
147         "headertag VARCHAR(255) DEFAULT 'none'",
148         "targettag VARCHAR(255) DEFAULT 'none'", 
149         "xmlmessage TEXT", 
150         "macaddress VARCHAR(17) DEFAULT 'none'",
151         "plainname VARCHAR(255) DEFAULT 'none'",
152         "siserver VARCHAR(255) DEFAULT 'none'",
153         "modified INTEGER DEFAULT '0'",
154 );
156 # holds all other gosa-si-server
157 our $known_server_db;
158 our $known_server_tn = "known_server";
159 my $known_server_file_name;
160 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)");
162 # holds all registrated clients
163 our $known_clients_db;
164 our $known_clients_tn = "known_clients";
165 my $known_clients_file_name;
166 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)");
168 # holds all registered clients at a foreign server
169 our $foreign_clients_db;
170 our $foreign_clients_tn = "foreign_clients"; 
171 my $foreign_clients_file_name;
172 my @foreign_clients_col_names = ("hostname VARCHAR(255)", "macaddress VARCHAR(17)", "regserver VARCHAR(255)", "timestamp VARCHAR(14)");
174 # holds all logged in user at each client 
175 our $login_users_db;
176 our $login_users_tn = "login_users";
177 my $login_users_file_name;
178 my @login_users_col_names = ("client VARCHAR(255)", "user VARCHAR(255)", "timestamp VARCHAR(14)", "regserver VARCHAR(255) DEFAULT 'localhost'");
180 # holds all fai server, the debian release and tag
181 our $fai_server_db;
182 our $fai_server_tn = "fai_server"; 
183 my $fai_server_file_name;
184 our @fai_server_col_names = ("timestamp VARCHAR(14)", "server VARCHAR(255)", "fai_release VARCHAR(255)", "sections VARCHAR(255)", "tag VARCHAR(255)"); 
186 our $fai_release_db;
187 our $fai_release_tn = "fai_release"; 
188 my $fai_release_file_name;
189 our @fai_release_col_names = ("timestamp VARCHAR(14)", "fai_release VARCHAR(255)", "class VARCHAR(255)", "type VARCHAR(255)", "state VARCHAR(255)"); 
191 # holds all packages available from different repositories
192 our $packages_list_db;
193 our $packages_list_tn = "packages_list";
194 my $packages_list_file_name;
195 our @packages_list_col_names = ("distribution VARCHAR(255)", "package VARCHAR(255)", "version VARCHAR(255)", "section VARCHAR(255)", "description TEXT", "template LONGBLOB", "timestamp VARCHAR(14)");
196 my $outdir = "/tmp/packages_list_db";
197 my $arch = "i386"; 
199 # holds all messages which should be delivered to a user
200 our $messaging_db;
201 our $messaging_tn = "messaging"; 
202 our @messaging_col_names = ("id INTEGER", "subject TEXT", "message_from VARCHAR(255)", "message_to VARCHAR(255)", 
203         "flag VARCHAR(255)", "direction VARCHAR(255)", "delivery_time VARCHAR(255)", "message TEXT", "timestamp VARCHAR(14)" );
204 my $messaging_file_name;
206 # path to directory to store client install log files
207 our $client_fai_log_dir = "/var/log/fai"; 
209 # queue which stores taskes until one of the $max_children children are ready to process the task
210 #my @tasks = qw();
211 my @msgs_to_decrypt = qw();
212 my $max_children = 2;
215 # loop delay for job queue to look for opsi jobs
216 my $job_queue_opsi_delay = 10;
217 our $opsi_client;
218 our $opsi_url;
219  
220 # Lifetime of logged in user information. If no update information comes after n seconds, 
221 # the user is expeceted to be no longer logged in or the host is no longer running. Because
222 # of this, the user is deleted from login_users_db
223 our $logged_in_user_date_of_expiry = 600;
225 # List of month names, used in function daemon_log
226 my @monthnames = ("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec");
228 %cfg_defaults = (
229 "general" => {
230     "log-file" => [\$log_file, "/var/run/".$prg.".log"],
231     "pid-file" => [\$pid_file, "/var/run/".$prg.".pid"],
232     },
233 "server" => {
234     "ip"                    => [\$server_ip, "0.0.0.0"],
235     "port"                  => [\$server_port, "20081"],
236     "known-clients"         => [\$known_clients_file_name, '/var/lib/gosa-si/clients.db' ],
237     "known-servers"         => [\$known_server_file_name, '/var/lib/gosa-si/servers.db'],
238     "incoming"              => [\$incoming_file_name, '/var/lib/gosa-si/incoming.db'],
239     "login-users"           => [\$login_users_file_name, '/var/lib/gosa-si/users.db'],
240     "fai-server"            => [\$fai_server_file_name, '/var/lib/gosa-si/fai_server.db'],
241     "fai-release"           => [\$fai_release_file_name, '/var/lib/gosa-si/fai_release.db'],
242     "packages-list"         => [\$packages_list_file_name, '/var/lib/gosa-si/packages.db'],
243     "messaging"             => [\$messaging_file_name, '/var/lib/gosa-si/messaging.db'],
244     "foreign-clients"       => [\$foreign_clients_file_name, '/var/lib/gosa-si/foreign_clients.db'],
245     "source-list"           => [\$sources_list, '/etc/apt/sources.list'],
246     "repo-path"             => [\$repo_path, '/srv/www/repository'],
247     "ldap-uri"              => [\$ldap_uri, ""],
248     "ldap-base"             => [\$ldap_base, ""],
249     "ldap-admin-dn"         => [\$ldap_admin_dn, ""],
250     "ldap-admin-password"   => [\$ldap_admin_password, ""],
251     "ldap-version"          => [\$ldap_version, 3],
252     "gosa-unit-tag"         => [\$gosa_unit_tag, ""],
253     "max-clients"           => [\$max_clients, 10],
254     "wol-password"          => [\$wake_on_lan_passwd, ""],
255         "mysql-username"        => [\$mysql_username, "gosa_si"],
256         "mysql-password"        => [\$mysql_password, ""],
257         "mysql-database"        => [\$mysql_database, "gosa_si"],
258         "mysql-host"            => [\$mysql_host, "127.0.0.1"],
259     },
260 "GOsaPackages" => {
261     "job-queue" => [\$job_queue_file_name, '/var/lib/gosa-si/jobs.db'],
262     "job-queue-loop-delay" => [\$job_queue_loop_delay, 3],
263     "messaging-db-loop-delay" => [\$messaging_db_loop_delay, 3],
264     "key" => [\$GosaPackages_key, "none"],
265                 "new-systems-ou" => [\$new_systems_ou, 'ou=workstations,ou=systems'],
266     },
267 "ClientPackages" => {
268     "key" => [\$ClientPackages_key, "none"],
269     "user-date-of-expiry" => [\$logged_in_user_date_of_expiry, 600],
270     },
271 "ServerPackages"=> {
272     "address"      => [\$foreign_server_string, ""],
273     "dns-lookup"            => [\$dns_lookup, "true"],
274     "domain"  => [\$server_domain, ""],
275     "key"     => [\$ServerPackages_key, "none"],
276     "key-lifetime" => [\$foreign_servers_register_delay, 120],
277     "job-synchronization-enabled" => [\$job_synchronization, "true"],
278     "synchronization-loop" => [\$modified_jobs_loop_delay, 5],
279     },
280 "ArpHandler" => {
281     "enabled"   => [\$arp_enabled, "true"],
282     "interface" => [\$arp_interface, "all"],
283         },
284 "Opsi" => {
285     "enabled"  => [\$opsi_enabled, "false"], 
286     "server"   => [\$opsi_server, "localhost"],
287     "admin"    => [\$opsi_admin, "opsi-admin"],
288     "password" => [\$opsi_password, "secret"],
289    },
291 );
294 #===  FUNCTION  ================================================================
295 #         NAME:  usage
296 #   PARAMETERS:  nothing
297 #      RETURNS:  nothing
298 #  DESCRIPTION:  print out usage text to STDERR
299 #===============================================================================
300 sub usage {
301     print STDERR << "EOF" ;
302 usage: $prg [-hvf] [-c config] [-d number]
304            -h        : this (help) message
305            -c <file> : config file
306            -f        : foreground, process will not be forked to background
307            -v        : be verbose (multiple to increase verbosity)
308                               'v': error logs
309                             'vvv': warning plus error logs                                              
310                           'vvvvv': info plus warning logs
311                         'vvvvvvv': debug plus info logs
312            -no-arp   : starts $prg without connection to arp module
313            -d <int>  : if verbose level is higher than 7x 'v' specified parts can be debugged
314                            1 : receiving messages
315                            2 : sending messages
316                            4 : encrypting/decrypting messages
317                            8 : verification if a message complies gosa-si requirements
318                           16 : message processing
319                           32 : ldap connectivity
320                           64 : database status and connectivity
321                          128 : main process 
322  
323 EOF
324     print "\n" ;
328 #===  FUNCTION  ================================================================
329 #         NAME:  logging
330 #   PARAMETERS:  level - string - default 'info'
331 #                msg - string -
332 #                facility - string - default 'LOG_DAEMON'
333 #      RETURNS:  nothing
334 #  DESCRIPTION:  function for logging
335 #===============================================================================
336 sub daemon_log {
337     my( $msg, $level ) = @_;
338     if (not defined $msg) { return }
339     if (not defined $level) { $level = 1 }
340         my $to_be_logged = 0;
342         # Write log line if line level is lower than verbosity given in commandline
343         if ($level <= $verbose) 
344         { 
345                 $to_be_logged = 1 ;
346         }
348         # Write if debug flag is set and bitstring matches
349         if ($debug_parts > 0)
350         {
351                 my $tmp_level = ($level - 10 >= 0) ? $level - 10 : 0 ;
352                 my $tmp_level_bitstring = unpack("B32", pack("N", $tmp_level));
353                 if (int($debug_parts_bitstring & $tmp_level_bitstring)) 
354                 {
355                         $to_be_logged = 1;
356                 }
357         }
359         if ($to_be_logged) 
360         {
361                 if(defined $log_file){
362                         my $open_log_fh = sysopen(LOG_HANDLE, $log_file, O_WRONLY | O_CREAT | O_APPEND , 0440);
363                         if(not $open_log_fh) {
364                                 print STDERR "cannot open $log_file: $!";
365                                 return;
366                         }
367                         # Check owner and group of log_file and update settings if necessary
368                         my ($dev, $ino, $mode, $nlink, $uid, $gid, $rdev, $size, $atime, $mtime, $ctime, $blksize, $blocks) = stat($log_file);
369                         if((not $uid eq $root_uid) || (not $gid eq $adm_gid)) {
370                                 chown($root_uid, $adm_gid, $log_file);
371                         }
373                         # Prepare time string for log message
374                         my ($seconds,$minutes,$hours,$monthday,$month,$year,$weekday,$yearday,$sommertime) = localtime(time);
375                         $hours = $hours < 10 ? $hours = "0".$hours : $hours;
376                         $minutes = $minutes < 10 ? $minutes = "0".$minutes : $minutes;
377                         $seconds = $seconds < 10 ? $seconds = "0".$seconds : $seconds;
378                         $month = $monthnames[$month];
379                         $monthday = $monthday < 10 ? $monthday = "0".$monthday : $monthday;
380                         $year+=1900;
382                         
383                         # Build log message and write it to log file and commandline
384                         chomp($msg);
385                         my $log_msg = "$month $monthday $hours:$minutes:$seconds $prg $msg\n";
386                         flock(LOG_HANDLE, LOCK_EX);
387                         seek(LOG_HANDLE, 0, 2);
388                         print LOG_HANDLE $log_msg;
389                         flock(LOG_HANDLE, LOCK_UN);
390                         if( $foreground ) 
391                         { 
392                                 print STDERR $log_msg;
393                         }
394                         close( LOG_HANDLE );
395                 }
396         }
400 #===  FUNCTION  ================================================================
401 #         NAME:  check_cmdline_param
402 #   PARAMETERS:  nothing
403 #      RETURNS:  nothing
404 #  DESCRIPTION:  validates commandline parameter
405 #===============================================================================
406 sub check_cmdline_param () {
407     my $err_counter = 0;
409         # Check configuration file
410         if(not defined($cfg_file)) {
411                 $cfg_file = "/etc/gosa-si/server.conf";
412                 if(! -r $cfg_file) {
413                         print STDERR "Please specify a config file.\n";
414                         $err_counter++;
415                 }
416     }
418         # Prepare identification which gosa-si parts should be debugged and which not
419         if (defined $debug_parts) 
420         {
421                 if ($debug_parts =~ /^\d+$/)
422                 {
423                         $debug_parts_bitstring = unpack("B32", pack("N", $debug_parts));
424                 }
425                 else
426                 {
427                         print STDERR "Value '$debug_parts' invalid for option d (number expected)\n";
428                         $err_counter++;
429                 }
430         }
432         # Exit if an error occour
433     if( $err_counter > 0 ) 
434         {
435         &usage( "", 1 );
436         exit( -1 );
437     }
441 #===  FUNCTION  ================================================================
442 #         NAME:  check_pid
443 #   PARAMETERS:  nothing
444 #      RETURNS:  nothing
445 #  DESCRIPTION:  handels pid processing
446 #===============================================================================
447 sub check_pid {
448     $pid = -1;
449     # Check, if we are already running
450     if( open(LOCK_FILE, "<$pid_file") ) {
451         $pid = <LOCK_FILE>;
452         if( defined $pid ) {
453             chomp( $pid );
454             if( -f "/proc/$pid/stat" ) {
455                 my($stat) = `cat /proc/$pid/stat` =~ m/$pid \((.+)\).*/;
456                 if( $stat ) {
457                                         print STDERR "\nERROR: Already running!\n";
458                     close( LOCK_FILE );
459                     exit -1;
460                 }
461             }
462         }
463         close( LOCK_FILE );
464         unlink( $pid_file );
465     }
467     # create a syslog msg if it is not to possible to open PID file
468     if (not sysopen(LOCK_FILE, $pid_file, O_WRONLY|O_CREAT|O_EXCL, 0644)) {
469         my($msg) = "Couldn't obtain lockfile '$pid_file' ";
470         if (open(LOCK_FILE, '<', $pid_file)
471                 && ($pid = <LOCK_FILE>))
472         {
473             chomp($pid);
474             $msg .= "(PID $pid)\n";
475         } else {
476             $msg .= "(unable to read PID)\n";
477         }
478         if( ! ($foreground) ) {
479             openlog( $0, "cons,pid", "daemon" );
480             syslog( "warning", $msg );
481             closelog();
482         }
483         else {
484             print( STDERR " $msg " );
485         }
486         exit( -1 );
487     }
490 #===  FUNCTION  ================================================================
491 #         NAME:  import_modules
492 #   PARAMETERS:  module_path - string - abs. path to the directory the modules 
493 #                are stored
494 #      RETURNS:  nothing
495 #  DESCRIPTION:  each file in module_path which ends with '.pm' and activation 
496 #                state is on is imported by "require 'file';"
497 #===============================================================================
498 sub import_modules {
499     if (not -e $modules_path) {
500         daemon_log("0 ERROR: cannot find directory or directory is not readable: $modules_path", 1);   
501     }
503     opendir (DIR, $modules_path) or die "ERROR while loading modules from directory $modules_path : $!\n";
505     while (defined (my $file = readdir (DIR))) {
506         if (not $file =~ /(\S*?).pm$/) {
507             next;
508         }
509                 my $mod_name = $1;
511         # ArpHandler switch
512         if( $file =~ /ArpHandler.pm/ ) {
513             if( $arp_enabled eq "false" ) { next; }
514         }
515         
516         eval { require $file; };
517         if ($@) {
518             daemon_log("0 ERROR: gosa-si-server could not load module $file", 1);
519             daemon_log("$@", 1);
520             exit;
521                 } else {
522                         my $info = eval($mod_name.'::get_module_info()');
523                         # Only load module if get_module_info() returns a non-null object
524                         if( $info ) {
525                                 my ($input_address, $input_key, $event_hash) = @{$info};
526                                 $known_modules->{$mod_name} = $info;
527                                 daemon_log("0 INFO: module $mod_name loaded", 5);
528                         }
529                 }
530     }   
531     close (DIR);
534 #===  FUNCTION  ================================================================
535 #         NAME:  password_check
536 #   PARAMETERS:  nothing
537 #      RETURNS:  nothing
538 #  DESCRIPTION:  escalates an critical error if two modules exist which are avaialable by 
539 #                the same password
540 #===============================================================================
541 sub password_check {
542     my $passwd_hash = {};
543     while (my ($mod_name, $mod_info) = each %$known_modules) {
544         my $mod_passwd = @$mod_info[1];
545         if (not defined $mod_passwd) { next; }
546         if (not exists $passwd_hash->{$mod_passwd}) {
547             $passwd_hash->{$mod_passwd} = $mod_name;
549         # escalates critical error
550         } else {
551             &daemon_log("0 ERROR: two loaded modules do have the same password. Please modify the 'key'-parameter in config file");
552             &daemon_log("0 ERROR: module='$mod_name' and module='".$passwd_hash->{$mod_passwd}."'");
553             exit( -1 );
554         }
555     }
560 #===  FUNCTION  ================================================================
561 #         NAME:  sig_int_handler
562 #   PARAMETERS:  signal - string - signal arose from system
563 #      RETURNS:  nothing
564 #  DESCRIPTION:  handels tasks to be done befor signal becomes active
565 #===============================================================================
566 sub sig_int_handler {
567     my ($signal) = @_;
569 #       if (defined($ldap_handle)) {
570 #               $ldap_handle->disconnect;
571 #       }
572     # TODO alle verbliebenden ldap verbindungen aus allen heaps beenden
573     
575     daemon_log("shutting down gosa-si-server", 1);
576     system("kill `ps -C gosa-si-server -o pid=`");
578 $SIG{INT} = \&sig_int_handler;
581 sub check_key_and_xml_validity {
582     my ($crypted_msg, $module_key, $session_id) = @_;
583     my $msg;
584     my $msg_hash;
585     my $error_string;
586     eval{
587         $msg = &decrypt_msg($crypted_msg, $module_key);
589         if ($msg =~ /<xml>/i){
590             $msg =~ s/\s+/ /g;  # just for better daemon_log
591             daemon_log("$session_id DEBUG: decrypted_msg: \n$msg", 18);
592             $msg_hash = $xml->XMLin($msg, ForceArray=>1);
594             ##############
595             # check header
596             if( not exists $msg_hash->{'header'} ) { die "no header specified"; }
597             my $header_l = $msg_hash->{'header'};
598             if( (1 > @{$header_l}) || ( ( 'HASH' eq ref @{$header_l}[0]) && (1 > keys %{@{$header_l}[0]}) ) ) { die 'empty header tag'; }
599             if( 1 < @{$header_l} ) { die 'more than one header specified'; }
600             my $header = @{$header_l}[0];
601             if( 0 == length $header) { die 'empty string in header tag'; }
603             ##############
604             # check source
605             if( not exists $msg_hash->{'source'} ) { die "no source specified"; }
606             my $source_l = $msg_hash->{'source'};
607             if( (1 > @{$source_l}) || ( ( 'HASH' eq ref @{$source_l}[0]) && (1 > keys %{@{$source_l}[0]}) ) ) { die 'empty source tag'; }
608             if( 1 < @{$source_l} ) { die 'more than one source specified'; }
609             my $source = @{$source_l}[0];
610             if( 0 == length $source) { die 'source error'; }
612             ##############
613             # check target
614             if( not exists $msg_hash->{'target'} ) { die "no target specified"; }
615             my $target_l = $msg_hash->{'target'};
616             if( (1 > @{$target_l}) || ( ('HASH' eq ref @{$target_l}[0]) && (1 > keys %{@{$target_l}[0]}) ) ) { die 'empty target tag'; }
617         }
618     };
619     if($@) {
620         daemon_log("$session_id ERROR: do not understand the message: $@", 1);
621         $msg = undef;
622         $msg_hash = undef;
623     }
625     return ($msg, $msg_hash);
629 sub check_outgoing_xml_validity {
630     my ($msg, $session_id) = @_;
632     my $msg_hash;
633     eval{
634         $msg_hash = $xml->XMLin($msg, ForceArray=>1);
636         ##############
637         # check header
638         my $header_l = $msg_hash->{'header'};
639         if( 1 != @{$header_l} ) {
640             die 'no or more than one headers specified';
641         }
642         my $header = @{$header_l}[0];
643         if( 0 == length $header) {
644             die 'header has length 0';
645         }
647         ##############
648         # check source
649         my $source_l = $msg_hash->{'source'};
650         if( 1 != @{$source_l} ) {
651             die 'no or more than 1 sources specified';
652         }
653         my $source = @{$source_l}[0];
654         if( 0 == length $source) {
655             die 'source has length 0';
656         }
658                 # Check if source contains hostname instead of ip address
659                 if($source =~ /^[a-z][\w-\.]+:\d+$/i) {
660                         my ($hostname,$port) = split(/:/, $source);
661                         my $ip_address = inet_ntoa(scalar gethostbyname($hostname));
662                         if(defined($ip_address) && $ip_address =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/ && $port =~ /^\d+$/) {
663                                 # Write ip address to $source variable
664                                 $source = "$ip_address:$port";
665                                 $msg_hash->{source}[0] = $source ;
666                                 $msg =~ s/<source>.*<\/source>/<source>$source<\/source>/; 
667                         }
668                 }
669         unless( $source =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
670                 $source =~ /^GOSA$/i) {
671             die "source '$source' is neither a complete ip-address with port nor 'GOSA'";
672         }
673         
674         ##############
675         # check target  
676         my $target_l = $msg_hash->{'target'};
677         if( 0 == @{$target_l} ) {
678             die "no targets specified";
679         }
680         foreach my $target (@$target_l) {
681             if( 0 == length $target) {
682                 die "target has length 0";
683             }
684             unless( $target =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
685                     $target =~ /^GOSA$/i ||
686                     $target =~ /^\*$/ ||
687                     $target =~ /KNOWN_SERVER/i ||
688                     $target =~ /JOBDB/i ||
689                     $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 ){
690                 die "target '$target' is not a complete ip-address with port or a valid target name or a mac-address";
691             }
692         }
693     };
694     if($@) {
695         daemon_log("$session_id ERROR: outgoing msg is not gosa-si envelope conform: $@", 1);
696         daemon_log("$@ ".(defined($msg) && length($msg)>0)?$msg:"Empty Message", 1);
697         $msg_hash = undef;
698     }
700     return ($msg, $msg_hash);
704 sub input_from_known_server {
705     my ($input, $remote_ip, $session_id) = @_ ;  
706     my ($msg, $msg_hash, $module);
708     my $sql_statement= "SELECT * FROM known_server";
709     my $query_res = $known_server_db->select_dbentry( $sql_statement ); 
711     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
712         my $host_name = $hit->{hostname};
713         if( not $host_name =~ "^$remote_ip") {
714             next;
715         }
716         my $host_key = $hit->{hostkey};
717         daemon_log("$session_id DEBUG: input_from_known_server: host_name: $host_name", 14);
718         daemon_log("$session_id DEBUG: input_from_known_server: host_key: $host_key", 14);
720         # check if module can open msg envelope with module key
721         my ($tmp_msg, $tmp_msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
722         if( (!$tmp_msg) || (!$tmp_msg_hash) ) {
723             daemon_log("$session_id DEBUG: input_from_known_server: deciphering raise error", 14);
724             daemon_log("$@", 14);
725             next;
726         }
727         else {
728             $msg = $tmp_msg;
729             $msg_hash = $tmp_msg_hash;
730             $module = "ServerPackages";
731             daemon_log("$session_id DEBUG: check_key_and_xml_validity... ok", 14);
732             last;
733         }
734     }
736     if( (!$msg) || (!$msg_hash) || (!$module) ) {
737         daemon_log("$session_id DEBUG: Incoming message is not from a known server", 14);
738     }
739   
740     return ($msg, $msg_hash, $module);
744 sub input_from_known_client {
745     my ($input, $remote_ip, $session_id) = @_ ;  
746     my ($msg, $msg_hash, $module);
748     my $sql_statement= "SELECT * FROM known_clients";
749     my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
750     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
751         my $host_name = $hit->{hostname};
752         if( not $host_name =~ /^$remote_ip/) {
753                 next;
754                 }
755         my $host_key = $hit->{hostkey};
756         &daemon_log("$session_id DEBUG: input_from_known_client: host_name: $host_name", 14);
757         &daemon_log("$session_id DEBUG: input_from_known_client: host_key: $host_key", 14);
759         # check if module can open msg envelope with module key
760         ($msg, $msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
762         if( (!$msg) || (!$msg_hash) ) {
763             &daemon_log("$session_id DEGUG: input_from_known_client: deciphering raise error", 14);
764             next;
765         }
766         else {
767             $module = "ClientPackages";
768             daemon_log("$session_id DEBUG: check_key_and_xml_validity... ok", 14);
769             last;
770         }
771     }
773     if( (!$msg) || (!$msg_hash) || (!$module) ) {
774         &daemon_log("$session_id DEBUG: Incoming message is not from a known client", 14);
775     }
777     return ($msg, $msg_hash, $module);
781 sub input_from_unknown_host {
782         no strict "refs";
783         my ($input, $session_id) = @_ ;
784         my ($msg, $msg_hash, $module);
785         my $error_string;
787         my %act_modules = %$known_modules;
789         while( my ($mod, $info) = each(%act_modules)) {
791                 # check a key exists for this module
792                 my $module_key = ${$mod."_key"};
793                 if( not defined $module_key ) {
794                         if( $mod eq 'ArpHandler' ) {
795                                 next;
796                         }
797                         daemon_log("$session_id ERROR: no key specified in config file for $mod", 1);
798                         next;
799                 }
800                 daemon_log("$session_id DEBUG: $mod: $module_key", 14);
802                 # check if module can open msg envelope with module key
803                 ($msg, $msg_hash) = &check_key_and_xml_validity($input, $module_key, $session_id);
804                 if( (not defined $msg) || (not defined $msg_hash) ) {
805                         next;
806                 } else {
807                         $module = $mod;
808             daemon_log("$session_id DEBUG: check_key_and_xml_validity... ok", 18);
809                         last;
810                 }
811         }
813         if( (!$msg) || (!$msg_hash) || (!$module)) {
814                 daemon_log("$session_id DEBUG: Incoming message is not from an unknown host", 14);
815         }
817         return ($msg, $msg_hash, $module);
821 sub create_ciphering {
822     my ($passwd) = @_;
823         if((!defined($passwd)) || length($passwd)==0) {
824                 $passwd = "";
825         }
826     $passwd = substr(md5_hex("$passwd") x 32, 0, 32);
827     my $iv = substr(md5_hex('GONICUS GmbH'),0, 16);
828     my $my_cipher = Crypt::Rijndael->new($passwd , Crypt::Rijndael::MODE_CBC());
829     $my_cipher->set_iv($iv);
830     return $my_cipher;
834 sub encrypt_msg {
835     my ($msg, $key) = @_;
836     my $my_cipher = &create_ciphering($key);
837     my $len;
838     {
839             use bytes;
840             $len= 16-length($msg)%16;
841     }
842     $msg = "\0"x($len).$msg;
843     $msg = $my_cipher->encrypt($msg);
844     chomp($msg = &encode_base64($msg));
845     # there are no newlines allowed inside msg
846     $msg=~ s/\n//g;
847     return $msg;
851 sub decrypt_msg {
853     my ($msg, $key) = @_ ;
854     $msg = &decode_base64($msg);
855     my $my_cipher = &create_ciphering($key);
856     $msg = $my_cipher->decrypt($msg); 
857     $msg =~ s/\0*//g;
858     return $msg;
862 sub get_encrypt_key {
863     my ($target) = @_ ;
864     my $encrypt_key;
865     my $error = 0;
867     # target can be in known_server
868     if( not defined $encrypt_key ) {
869         my $sql_statement= "SELECT * FROM known_server WHERE hostname='$target'";
870         my $query_res = $known_server_db->select_dbentry( $sql_statement ); 
871         while( my ($hit_num, $hit) = each %{ $query_res } ) {    
872             my $host_name = $hit->{hostname};
873             if( $host_name ne $target ) {
874                 next;
875             }
876             $encrypt_key = $hit->{hostkey};
877             last;
878         }
879     }
881     # target can be in known_client
882     if( not defined $encrypt_key ) {
883         my $sql_statement= "SELECT * FROM known_clients WHERE hostname='$target'";
884         my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
885         while( my ($hit_num, $hit) = each %{ $query_res } ) {    
886             my $host_name = $hit->{hostname};
887             if( $host_name ne $target ) {
888                 next;
889             }
890             $encrypt_key = $hit->{hostkey};
891             last;
892         }
893     }
895     return $encrypt_key;
899 #===  FUNCTION  ================================================================
900 #         NAME:  open_socket
901 #   PARAMETERS:  PeerAddr string something like 192.168.1.1 or 192.168.1.1:10000
902 #                [PeerPort] string necessary if port not appended by PeerAddr
903 #      RETURNS:  socket IO::Socket::INET
904 #  DESCRIPTION:  open a socket to PeerAddr
905 #===============================================================================
906 sub open_socket {
907     my ($PeerAddr, $PeerPort) = @_ ;
908     if(defined($PeerPort)){
909         $PeerAddr = $PeerAddr.":".$PeerPort;
910     }
911     my $socket;
912     $socket = new IO::Socket::INET(PeerAddr => $PeerAddr,
913             Porto => "tcp",
914             Type => SOCK_STREAM,
915             Timeout => 5,
916             );
917     if(not defined $socket) {
918         return;
919     }
920 #    &daemon_log("DEBUG: open_socket: $PeerAddr", 7);
921     return $socket;
925 sub send_msg_to_target {
926     my ($msg, $address, $encrypt_key, $msg_header, $session_id) = @_ ;
927     my $error = 0;
928     my $header;
929     my $timestamp = &get_time();
930     my $new_status;
931     my $act_status;
932     my ($sql_statement, $res);
933   
934     if( $msg_header ) {
935         $header = "'$msg_header'-";
936     } else {
937         $header = "";
938     }
940         # Memorize own source address
941         my $own_source_address = &get_local_ip_for_remote_ip(sprintf("%s", $address =~ /^([0-9\.]*?):.*$/));
942         $own_source_address .= ":".$server_port;
944         # Patch 0.0.0.0 source to real address
945         $msg =~ s/<source>(0\.0\.0\.0):(\d*?)<\/source>/<source>$own_source_address<\/source>/s;
946         # Patch GOSA source to real address and add forward_to_gosa tag
947         $msg =~ s/<source>GOSA<\/source>/<source>$own_source_address<\/source> <forward_to_gosa>$own_source_address,$session_id<\/forward_to_gosa>/ ;
949     # encrypt xml msg
950     my $crypted_msg = &encrypt_msg($msg, $encrypt_key);
952     # opensocket
953     my $socket = &open_socket($address);
954     if( !$socket ) {
955         daemon_log("$session_id ERROR: Cannot open socket to host '$address'. Message processing aborted!", 1);
956         $error++;
957     }
958     
959     if( $error == 0 ) {
960         # send xml msg
961         print $socket $crypted_msg.";$own_source_address\n";
962         daemon_log("$session_id INFO: send ".$header."msg to $address", 5);
963         daemon_log("$session_id DEBUG: message:\n$msg", 12);
964         
965     }
967     # close socket in any case
968     if( $socket ) {
969         close $socket;
970     }
972     if( $error > 0 ) { $new_status = "down"; }
973     else { $new_status = $msg_header; }
976     # known_clients
977     $sql_statement = "SELECT * FROM $known_clients_tn WHERE hostname='$address'";
978     $res = $known_clients_db->select_dbentry($sql_statement);
979     if( keys(%$res) == 1) {
980         $act_status = exists $res->{1}->{'status'} ? $res->{1}->{'status'} : "";
981         if ($act_status eq "down" && $new_status eq "down") {
982             $sql_statement = "DELETE FROM known_clients WHERE hostname='$address'";
983             $res = $known_clients_db->del_dbentry($sql_statement);
984             daemon_log("$session_id WARNING: failed 2x to send msg to host '$address', delete host from known_clients", 3);
985         } else { 
986             $sql_statement = "UPDATE known_clients SET status='$new_status', timestamp='$timestamp' WHERE hostname='$address'";
987             $res = $known_clients_db->update_dbentry($sql_statement);
988             if($new_status eq "down"){
989                 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
990             } else {
991                 daemon_log("$session_id INFO: set '$address' from status '$act_status' to '$new_status'", 5);
992             }
993         }
994     }
996     # known_server
997     $sql_statement = "SELECT * FROM $known_server_tn WHERE hostname='$address'";
998     $res = $known_server_db->select_dbentry($sql_statement);
999     if( keys(%$res) == 1) {
1000         $act_status = exists $res->{1}->{'status'} ? $res->{1}->{'status'} : "";
1001         if ($act_status eq "down" && $new_status eq "down") {
1002             $sql_statement = "DELETE FROM known_server WHERE hostname='$address'";
1003             $res = $known_server_db->del_dbentry($sql_statement);
1004             daemon_log("$session_id WARNING: failed 2x to send a message to host '$address', delete host from known_server", 3);
1005         } 
1006         else { 
1007             $sql_statement = "UPDATE known_server SET status='$new_status', timestamp='$timestamp' WHERE hostname='$address'";
1008             $res = $known_server_db->update_dbentry($sql_statement);
1009             if($new_status eq "down"){
1010                 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
1011             } else {
1012                 daemon_log("$session_id INFO: set '$address' from status '$act_status' to '$new_status'", 5);
1013             }
1014         }
1015     }
1016     return $error; 
1020 sub update_jobdb_status_for_send_msgs {
1021     my ($session_id, $answer, $error) = @_;
1022     if( $answer =~ /<jobdb_id>(\d+)<\/jobdb_id>/ ) {
1023                 &daemon_log("$session_id DEBUG: try to update job status", 138); 
1024         my $jobdb_id = $1;
1025     
1026         $answer =~ /<header>(.*)<\/header>/;
1027         my $job_header = $1;
1029         $answer =~ /<target>(.*)<\/target>/;
1030         my $job_target = $1;
1031             
1032         # Sending msg failed
1033         if( $error ) {
1035             # Set jobs to done, jobs do not need to deliver their message in any case
1036             if (($job_header eq "trigger_action_localboot")
1037                     ||($job_header eq "trigger_action_lock")
1038                     ||($job_header eq "trigger_action_halt") 
1039                     ) {
1040                 my $sql_statement = "UPDATE $job_queue_tn SET status='done' WHERE id=$jobdb_id";
1041                 my $res = $job_db->update_dbentry($sql_statement);
1042                 
1043             # Reactivate jobs, jobs need to deliver their message
1044             } elsif (($job_header eq "trigger_action_activate")
1045                     ||($job_header eq "trigger_action_update")
1046                     ||($job_header eq "trigger_action_reinstall") 
1047                     ||($job_header eq "trigger_activate_new")
1048                     ) {
1049                 &reactivate_job_with_delay($session_id, $job_target, $job_header, 30 );
1051             # For all other messages
1052             } else {
1053                 my $sql_statement = "UPDATE $job_queue_tn ".
1054                     "SET status='error', result='can not deliver msg, please consult log file' ".
1055                     "WHERE id=$jobdb_id";
1056                 my $res = $job_db->update_dbentry($sql_statement);
1057             }
1059         # Sending msg was successful
1060         } else {
1061             # Set jobs localboot, lock, activate, halt, reboot and wake to done
1062             # jobs reinstall, update, inst_update do themself setting to done
1063             if (($job_header eq "trigger_action_localboot")
1064                     ||($job_header eq "trigger_action_lock")
1065                     ||($job_header eq "trigger_action_activate")
1066                     ||($job_header eq "trigger_action_halt") 
1067                     ||($job_header eq "trigger_action_reboot")
1068                     ||($job_header eq "trigger_action_wake")
1069                     ||($job_header eq "trigger_wake")
1070                     ) {
1072                 my $sql_statement = "UPDATE $job_queue_tn ".
1073                     "SET status='done' ".
1074                     "WHERE id=$jobdb_id AND status='processed'";
1075                 my $res = $job_db->update_dbentry($sql_statement);
1076             } else { 
1077                 &daemon_log("$session_id DEBUG: sending message succeed but cannot update job status.", 138); 
1078             } 
1079         } 
1080     } else { 
1081         &daemon_log("$session_id DEBUG: cannot update job status, msg has no jobdb_id-tag.", 138); 
1082     }
1085 sub reactivate_job_with_delay {
1086     my ($session_id, $target, $header, $delay) = @_ ;
1087     # 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
1088     
1089     if (not defined $delay) { $delay = 30 } ;
1090     my $delay_timestamp = &calc_timestamp(&get_time(), "plus", $delay);
1092     my $sql = "UPDATE $job_queue_tn Set timestamp='$delay_timestamp', status='waiting' WHERE (macaddress LIKE 'target' AND headertag='$header')"; 
1093     my $res = $job_db->update_dbentry($sql);
1094     daemon_log("$session_id INFO: '$header'-job will be reactivated at '$delay_timestamp' ".
1095             "cause client '$target' is currently not available", 5);
1096     return;
1100 sub sig_handler {
1101         my ($kernel, $signal) = @_[KERNEL, ARG0] ;
1102         daemon_log("0 INFO got signal '$signal'", 1); 
1103         $kernel->sig_handled();
1104         return;
1108 sub msg_to_decrypt {
1109         my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
1110         my $session_id = $session->ID;
1111         my ($msg, $msg_hash, $module);
1112         my $error = 0;
1114         # fetch new msg out of @msgs_to_decrypt
1115         my $tmp_next_msg = shift @msgs_to_decrypt;
1116     my ($next_msg, $msg_source) = split(/;/, $tmp_next_msg);
1118         # msg is from a new client or gosa
1119         ($msg, $msg_hash, $module) = &input_from_unknown_host($next_msg, $session_id);
1121         # msg is from a gosa-si-server
1122         if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1123                 if (not defined $msg_source) 
1124                 {
1125                         # Only needed, to be compatible with older gosa-si-server versions
1126                         ($msg, $msg_hash, $module) = &input_from_known_server($next_msg, $heap->{'remote_ip'}, $session_id);
1127                 }
1128                 else
1129                 {
1130                         ($msg, $msg_hash, $module) = &input_from_known_server($next_msg, $msg_source, $session_id);
1131                 }
1132         }
1133         # msg is from a gosa-si-client
1134         if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1135                 ($msg, $msg_hash, $module) = &input_from_known_client($next_msg, $msg_source, $session_id);
1136         }
1137         # an error occurred
1138         if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1139                 # If an incoming msg could not be decrypted (maybe a wrong key), decide if msg comes from a client 
1140                 # or a server.  In case of a client, send a ping. If the client could not understand a msg from its 
1141                 # server the client cause a re-registering process. In case of a server, decrease update_time in kown_server_db
1142                 # and trigger a re-registering process for servers
1143                 if (defined $msg_source && $msg_source =~ /:$server_port$/)
1144                 {
1145                         daemon_log("$session_id WARNING: Cannot understand incoming msg from server '$msg_source'. Cause re-registration process for servers.", 3);
1146                         my $update_statement = "UPDATE $known_server_tn SET update_time='19700101000000' WHERE hostname='$msg_source'"; 
1147                         daemon_log("$session_id DEBUG: $update_statement", 7);
1148                         my $upadte_res = $known_server_db->exec_statement($update_statement);
1149                         $kernel->yield("register_at_foreign_servers");
1150                 }
1151                 elsif (defined $msg_source)
1152                 {
1153                         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);
1154                         #my $remote_ip = $heap->{'remote_ip'};
1155                         #my $remote_port = $heap->{'remote_port'};
1156                         my $ping_msg = "<xml> <header>gosa_ping</header> <source>$server_address</source><target>$msg_source</target></xml>";
1157                         my ($test_error, $test_error_string) = &send_msg_to_target($ping_msg, "$msg_source", "dummy-key", "gosa_ping", $session_id);
1158                         daemon_log("$session_id WARNING: sending msg to cause re-registering: $ping_msg", 3);
1159                 }
1160                 else
1161                 {
1162                         my $foreign_host = defined $msg_source ? $msg_source : $heap->{'remote_ip'};
1163                         daemon_log("$session_id ERROR: incoming message from host '$foreign_host' cannot be understood. Processing aborted: $tmp_next_msg", 1);
1164                 }
1166                 $error++
1167         }
1170         my $header;
1171         my $target;
1172         my $source;
1173         my $done = 0;
1174         my $sql;
1175         my $res;
1177         # check whether this message should be processed here
1178         if ($error == 0) {
1179                 $header = @{$msg_hash->{'header'}}[0];
1180                 $target = @{$msg_hash->{'target'}}[0];
1181                 $source = @{$msg_hash->{'source'}}[0];
1182                 my $not_found_in_known_clients_db = 0;
1183                 my $not_found_in_known_server_db = 0;
1184                 my $not_found_in_foreign_clients_db = 0;
1185                 my $local_address;
1186                 my $local_mac;
1187                 my ($target_ip, $target_port) = split(':', $target);
1189                 # Determine the local ip address if target is an ip address
1190                 if ($target =~ /^\d+\.\d+\.\d+\.\d+:\d+$/) {
1191                         $local_address = &get_local_ip_for_remote_ip($target_ip).":$server_port";
1192                 } else {
1193                         $local_address = $server_address;
1194                 }
1196                 # Determine the local mac address if target is a mac address
1197                 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) {
1198                         my $loc_ip = &get_local_ip_for_remote_ip($heap->{'remote_ip'});
1199                         my $network_interface= &get_interface_for_ip($loc_ip);
1200                         $local_mac = &get_mac_for_interface($network_interface);
1201                 } else {
1202                         $local_mac = $server_mac_address;
1203                 }
1205                 # target and source is equal to GOSA -> process here
1206                 if (not $done) {
1207                         if ($target eq "GOSA" && $source eq "GOSA") {
1208                                 $done = 1;                    
1209                                 &daemon_log("$session_id DEBUG: target and source is 'GOSA' -> process '$header' here", 11);
1210                         }
1211                 }
1213                 # target is own address without forward_to_gosa-tag -> process here
1214                 if (not $done) {
1215                         #if ((($target eq $local_address) || ($target eq $local_mac) ) && (not exists $msg_hash->{'forward_to_gosa'})) {
1216                         if (($target eq $local_address) && (not exists $msg_hash->{'forward_to_gosa'})) {
1217                                 $done = 1;
1218                                 if ($source eq "GOSA") {
1219                                         $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1220                                 }
1221                                 &daemon_log("$session_id DEBUG: target is own address without forward_to_gosa-tag -> process '$header' here", 11);
1222                         }
1223                 }
1225                 # target is own address with forward_to_gosa-tag not pointing to myself -> process here
1226                 if (not $done) {
1227                         my $forward_to_gosa =  @{$msg_hash->{'forward_to_gosa'}}[0];
1228                         my $gosa_at;
1229                         my $gosa_session_id;
1230                         if (($target eq $local_address) && (defined $forward_to_gosa)){
1231                                 my ($gosa_at, $gosa_session_id) = split(/,/, $forward_to_gosa);
1232                                 if ($gosa_at ne $local_address) {
1233                                         $done = 1;
1234                                         &daemon_log("$session_id DEBUG: target is own address with forward_to_gosa-tag not pointing to myself -> process '$header' here", 11); 
1235                                 }
1236                         }
1237                 }
1239                 # Target is a client address and there is a processing function within a plugin -> process loaclly
1240                 if (not $done)
1241                 {
1242                         # Check if target is a client address
1243                         $sql = "SELECT * FROM $known_clients_tn WHERE (hostname='$target' OR macaddress LIKE '$target')"; 
1244                         $res = $known_clients_db->select_dbentry($sql);
1245                         if ((keys(%$res) > 0) ) 
1246                         {
1247                                 my $hostname = $res->{1}->{'hostname'};
1248                                 my $reduced_header = $header;
1249                                 $reduced_header =~ s/gosa_//;
1250                                 # Check if there is a processing function within a plugin
1251                                 if (exists $known_functions->{$reduced_header}) 
1252                                 {
1253                                         $msg =~ s/<target>\S*<\/target>/<target>$hostname<\/target>/;
1254                                         $done = 1;
1255                                         &daemon_log("$session_id DEBUG: Target is client address with processing function within a plugin -> process '$header' here", 11);
1256                                 }
1257                         }
1258                 }
1260                 # If header has a 'job_' prefix, do always process message locally
1261                 # which means put it into job queue
1262                 if ((not $done) && ($header =~ /job_/))
1263                 {
1264                         $done = 1;
1265                         &daemon_log("$session_id DEBUG: Header has a 'job_' prefix. Put it into job queue. -> process '$header' here", 11);
1266                 }
1268                 # if message should be processed here -> add message to incoming_db
1269                 if ($done) {
1270                         # if a 'job_' or a 'gosa_' message comes from a foreign server, fake module from
1271                         # ServerPackages to GosaPackages so gosa-si-server knows how to process this kind of messages
1272                         if ($header =~ /^gosa_/ || $header =~ /^job_/) {
1273                                 $module = "GosaPackages";
1274                         }
1276                         my $res = $incoming_db->add_dbentry( {table=>$incoming_tn,
1277                                         primkey=>[],
1278                                         headertag=>$header,
1279                                         targettag=>$target,
1280                                         xmlmessage=>&encode_base64($msg),
1281                                         timestamp=>&get_time,
1282                                         module=>$module,
1283                                         sessionid=>$session_id,
1284                                 } );
1286                 }
1288                 # target is own address with forward_to_gosa-tag pointing at myself -> forward to gosa
1289                 if (not $done) {
1290                         my $forward_to_gosa =  @{$msg_hash->{'forward_to_gosa'}}[0];
1291                         my $gosa_at;
1292                         my $gosa_session_id;
1293                         if (($target eq $local_address) && (defined $forward_to_gosa)){
1294                                 my ($gosa_at, $gosa_session_id) = split(/,/, $forward_to_gosa);
1295                                 if ($gosa_at eq $local_address) {
1296                                         my $session_reference = $kernel->ID_id_to_session($gosa_session_id);
1297                                         if( defined $session_reference ) {
1298                                                 $heap = $session_reference->get_heap();
1299                                         }
1300                                         if(exists $heap->{'client'}) {
1301                                                 $msg = &encrypt_msg($msg, $GosaPackages_key);
1302                                                 $heap->{'client'}->put($msg);
1303                                                 &daemon_log("$session_id DEBUG: incoming '$header' message forwarded to GOsa", 11); 
1304                                         }
1305                                         $done = 1;
1306                                         &daemon_log("$session_id DEBUG: target is own address with forward_to_gosa-tag pointing at myself -> forward '$header' to gosa", 11);
1307                                 }
1308                         }
1310                 }
1312                 # target is a client address in known_clients -> forward to client
1313                 if (not $done) {
1314                         $sql = "SELECT * FROM $known_clients_tn WHERE (hostname='$target' OR macaddress LIKE '$target')"; 
1315                         $res = $known_clients_db->select_dbentry($sql);
1316                         if (keys(%$res) > 0) 
1317                         {
1318                                 $done = 1; 
1319                                 &daemon_log("$session_id DEBUG: target is a client address in known_clients -> forward '$header' to client", 11);
1320                                 my $hostkey = $res->{1}->{'hostkey'};
1321                                 my $hostname = $res->{1}->{'hostname'};
1322                                 $msg =~ s/<target>\S*<\/target>/<target>$hostname<\/target>/;
1323                                 $msg =~ s/<header>gosa_/<header>/;
1324                                 my $error= &send_msg_to_target($msg, $hostname, $hostkey, $header, $session_id);
1325                                 if ($error) {
1326                                         &daemon_log("$session_id ERROR: Some problems occurred while trying to send msg to client '$hostkey': $msg", 1);
1327                                 }
1328                         } 
1329                         else 
1330                         {
1331                                 $not_found_in_known_clients_db = 1;
1332                         }
1333                 }
1335                 # target is a client address in foreign_clients -> forward to registration server
1336                 if (not $done) {
1337                         $sql = "SELECT * FROM $foreign_clients_tn WHERE (hostname='$target' OR macaddress LIKE '$target')";
1338                         $res = $foreign_clients_db->select_dbentry($sql);
1339                         if (keys(%$res) > 0) {
1340                                 my $hostname = $res->{1}->{'hostname'};
1341                                 my ($host_ip, $host_port) = split(/:/, $hostname);
1342                                 my $local_address =  &get_local_ip_for_remote_ip($host_ip).":$server_port";
1343                                 my $regserver = $res->{1}->{'regserver'};
1344                                 my $sql = "SELECT * FROM $known_server_tn WHERE hostname='$regserver'"; 
1345                                 my $res = $known_server_db->select_dbentry($sql);
1346                                 if (keys(%$res) > 0) {
1347                                         my $regserver_key = $res->{1}->{'hostkey'};
1348                                         $msg =~ s/<source>GOSA<\/source>/<source>$local_address<\/source>/;
1349                                         $msg =~ s/<target>$target<\/target>/<target>$hostname<\/target>/;
1350                                         if ($source eq "GOSA") {
1351                                                 $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1352                                         }
1353                                         my $error= &send_msg_to_target($msg, $regserver, $regserver_key, $header, $session_id);
1354                                         if ($error) {
1355                                                 &daemon_log("$session_id ERROR: some problems occurred while trying to send msg to registration server: $msg", 1); 
1356                                         }
1357                                 }
1358                                 $done = 1;
1359                                 &daemon_log("$session_id DEBUG: target is a client address in foreign_clients -> forward '$header' to registration server", 11);
1360                         } else {
1361                                 $not_found_in_foreign_clients_db = 1;
1362                         }
1363                 }
1365                 # target is a server address -> forward to server
1366                 if (not $done) {
1367                         $sql = "SELECT * FROM $known_server_tn WHERE (hostname='$target' OR macaddress LIKE '$target')";
1368                         $res = $known_server_db->select_dbentry($sql);
1369                         if (keys(%$res) > 0) {
1370                                 my $hostkey = $res->{1}->{'hostkey'};
1372                                 if ($source eq "GOSA") {
1373                                         $msg =~ s/<source>GOSA<\/source>/<source>$local_address<\/source>/;
1374                                         $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1376                                 }
1378                                 &send_msg_to_target($msg, $target, $hostkey, $header, $session_id);
1379                                 $done = 1;
1380                                 &daemon_log("$session_id DEBUG: target is a server address -> forward '$header' to server", 11);
1381                         } else {
1382                                 $not_found_in_known_server_db = 1;
1383                         }
1384                 }
1387                 # target is not in foreign_clients_db, known_server_db or known_clients_db, maybe it is a complete new one -> process here
1388                 if ( $not_found_in_foreign_clients_db 
1389                         && $not_found_in_known_server_db
1390                         && $not_found_in_known_clients_db) {
1391                         &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);
1392             if ($header =~ /^gosa_/ || $header =~ /^job_/) { 
1393                 $module = "GosaPackages"; 
1394             }
1395                         my $res = $incoming_db->add_dbentry( {table=>$incoming_tn,
1396                                         primkey=>[],
1397                                         headertag=>$header,
1398                                         targettag=>$target,
1399                                         xmlmessage=>&encode_base64($msg),
1400                                         timestamp=>&get_time,
1401                                         module=>$module,
1402                                         sessionid=>$session_id,
1403                                 } );
1404                         $done = 1;
1405                 }
1408                 if (not $done) {
1409                         daemon_log("$session_id ERROR: do not know what to do with this message: $msg", 1);
1410                         if ($source eq "GOSA") {
1411                                 my %data = ('error_msg' => &encode_base64($msg), 'error_string' => "Do not know what to do with this message!");
1412                                 my $error_msg = &build_msg("error", $local_address, "GOSA", \%data ); 
1414                                 my $session_reference = $kernel->ID_id_to_session($session_id);
1415                                 if( defined $session_reference ) {
1416                                         $heap = $session_reference->get_heap();
1417                                 }
1418                                 if(exists $heap->{'client'}) {
1419                                         $error_msg = &encrypt_msg($error_msg, $GosaPackages_key);
1420                                         $heap->{'client'}->put($error_msg);
1421                                 }
1422                         }
1423                 }
1425         }
1427         return;
1431 sub next_task {
1432     my ($session, $heap, $task) = @_[SESSION, HEAP, ARG0, ARG1];
1433     my $running_task = POE::Wheel::Run->new(
1434             Program => sub { process_task($session, $heap, $task) },
1435             StdioFilter => POE::Filter::Reference->new(),
1436             StdoutEvent  => "task_result",
1437             StderrEvent  => "task_debug",
1438             CloseEvent   => "task_done",
1439             );
1440     $heap->{task}->{ $running_task->ID } = $running_task;
1443 sub handle_task_result {
1444     my ($kernel, $heap, $result) = @_[KERNEL, HEAP, ARG0];
1445     my $client_answer = $result->{'answer'};
1446     if( $client_answer =~ s/session_id=(\d+)$// ) {
1447         my $session_id = $1;
1448         if( defined $session_id ) {
1449             my $session_reference = $kernel->ID_id_to_session($session_id);
1450             if( defined $session_reference ) {
1451                 $heap = $session_reference->get_heap();
1452             }
1453         }
1455         if(exists $heap->{'client'}) {
1456             $heap->{'client'}->put($client_answer);
1457         }
1458     }
1459     $kernel->sig(CHLD => "child_reap");
1462 sub handle_task_debug {
1463     my $result = $_[ARG0];
1464     print STDERR "$result\n";
1467 sub handle_task_done {
1468     my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
1469     delete $heap->{task}->{$task_id};
1470         if (exists $heap->{ldap_handle}->{$task_id}) {
1471                 &release_ldap_handle($heap->{ldap_handle}->{$task_id});
1472         }
1475 sub process_task {
1476     no strict "refs";
1477     #CHECK: Not @_[...]?
1478     my ($session, $heap, $task) = @_;
1479     my $error = 0;
1480     my $answer_l;
1481     my ($answer_header, @answer_target_l, $answer_source);
1482     my $client_answer = "";
1484     # prepare all variables needed to process message
1485     #my $msg = $task->{'xmlmessage'};
1486     my $msg = &decode_base64($task->{'xmlmessage'});
1487     my $incoming_id = $task->{'id'};
1488     my $module = $task->{'module'};
1489     my $header =  $task->{'headertag'};
1490     my $session_id = $task->{'sessionid'};
1491                 my $msg_hash;
1492                 eval {
1493         $msg_hash = $xml->XMLin($msg, ForceArray=>1);
1494                 }; 
1495                 daemon_log("ERROR: XML failure '$@'") if ($@);
1496     my $source = @{$msg_hash->{'source'}}[0];
1497     
1498     # set timestamp of incoming client uptodate, so client will not 
1499     # be deleted from known_clients because of expiration
1500     my $cur_time = &get_time();
1501     my $sql = "UPDATE $known_clients_tn SET timestamp='$cur_time' WHERE hostname='$source'"; 
1502     my $res = $known_clients_db->exec_statement($sql);
1504     ######################
1505     # process incoming msg
1506     if( $error == 0) {
1507         daemon_log("$session_id INFO: Incoming msg (session_id=$session_id) with header '".@{$msg_hash->{'header'}}[0]."'", 5); 
1508         daemon_log("$session_id DEBUG: Processing module ".$module, 26);
1509         $answer_l = &{ $module."::process_incoming_msg" }($msg, $msg_hash, $session_id);
1511         if ( 0 < @{$answer_l} ) {
1512             my $answer_str = join("\n", @{$answer_l});
1513                         my @headers; 
1514             while ($answer_str =~ /<header>(\w+)<\/header>/g) {
1515                                 push(@headers, $1);
1516             }
1517                         daemon_log("$session_id INFO: got answer message(s) with header: '".join("', '", @headers)."'", 5);
1518             daemon_log("$session_id DEBUG: $module: got answer from module: \n".$answer_str,26);
1519         } else {
1520             daemon_log("$session_id DEBUG: $module: got no answer from module!" ,26);
1521         }
1523     }
1524     if( !$answer_l ) { $error++ };
1526     ########
1527     # answer
1528     if( $error == 0 ) {
1530         foreach my $answer ( @{$answer_l} ) {
1531             # check outgoing msg to xml validity
1532             my ($answer, $answer_hash) = &check_outgoing_xml_validity($answer, $session_id);
1533             if( not defined $answer_hash ) { next; }
1534             
1535             $answer_header = @{$answer_hash->{'header'}}[0];
1536             @answer_target_l = @{$answer_hash->{'target'}};
1537             $answer_source = @{$answer_hash->{'source'}}[0];
1539             # deliver msg to all targets 
1540             foreach my $answer_target ( @answer_target_l ) {
1542                 # targets of msg are all gosa-si-clients in known_clients_db
1543                 if( $answer_target eq "*" ) {
1544                     # answer is for all clients
1545                     my $sql_statement= "SELECT * FROM known_clients";
1546                     my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
1547                     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1548                         my $host_name = $hit->{hostname};
1549                         my $host_key = $hit->{hostkey};
1550                         my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1551                         &update_jobdb_status_for_send_msgs($session_id, $answer, $error);
1552                     }
1553                 }
1555                 # targets of msg are all gosa-si-server in known_server_db
1556                 elsif( $answer_target eq "KNOWN_SERVER" ) {
1557                     # answer is for all server in known_server
1558                     my $sql_statement= "SELECT * FROM $known_server_tn";
1559                     my $query_res = $known_server_db->select_dbentry( $sql_statement ); 
1560                     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1561                         my $host_name = $hit->{hostname};
1562                         my $host_key = $hit->{hostkey};
1563                         $answer =~ s/<target>\S+<\/target>/<target>$host_name<\/target>/g;
1564                         my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1565                         &update_jobdb_status_for_send_msgs($session_id, $answer, $error);
1566                     }
1567                 }
1569                 # target of msg is GOsa
1570                                 elsif( $answer_target eq "GOSA" ) {
1571                                         my $session_id = ($1) if $answer =~ /<session_id>(\d+?)<\/session_id>/;
1572                                         my $add_on = "";
1573                     if( defined $session_id ) {
1574                         $add_on = ".session_id=$session_id";
1575                     }
1576                     # answer is for GOSA and has to returned to connected client
1577                     my $gosa_answer = &encrypt_msg($answer, $GosaPackages_key);
1578                     $client_answer = $gosa_answer.$add_on;
1579                 }
1581                 # target of msg is job queue at this host
1582                 elsif( $answer_target eq "JOBDB") {
1583                     $answer =~ /<header>(\S+)<\/header>/;   
1584                     my $header;
1585                     if( defined $1 ) { $header = $1; }
1586                     my $error = &send_msg_to_target($answer, $server_address, $GosaPackages_key, $header, $session_id);
1587                     &update_jobdb_status_for_send_msgs($session_id, $answer, $error);
1588                 }
1590                 # Target of msg is a mac address
1591                 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 ) {
1592                     daemon_log("$session_id INFO: target is mac address '$answer_target', looking for host in known_clients and foreign_clients", 5);
1594                     # Looking for macaddress in known_clients
1595                     my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$answer_target'";
1596                     my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1597                     my $found_ip_flag = 0;
1598                     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1599                         my $host_name = $hit->{hostname};
1600                         my $host_key = $hit->{hostkey};
1601                         $answer =~ s/$answer_target/$host_name/g;
1602                         daemon_log("$session_id INFO: found host '$host_name', associated to '$answer_target'", 5);
1603                         my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1604                         &update_jobdb_status_for_send_msgs($session_id, $answer, $error);
1605                         $found_ip_flag++ ;
1606                     }   
1608                     # Looking for macaddress in foreign_clients
1609                     if ($found_ip_flag == 0) {
1610                         my $sql = "SELECT * FROM $foreign_clients_tn WHERE macaddress LIKE '$answer_target'";
1611                         my $res = $foreign_clients_db->select_dbentry($sql);
1612                         while( my ($hit_num, $hit) = each %{ $res } ) {
1613                             my $host_name = $hit->{hostname};
1614                             my $reg_server = $hit->{regserver};
1615                             daemon_log("$session_id INFO: found host '$host_name' with mac '$answer_target', registered at '$reg_server'", 5);
1616                             
1617                             # Fetch key for reg_server
1618                             my $reg_server_key;
1619                             my $sql = "SELECT * FROM $known_server_tn WHERE hostname='$reg_server'";
1620                             my $res = $known_server_db->select_dbentry($sql);
1621                             if (exists $res->{1}) {
1622                                 $reg_server_key = $res->{1}->{'hostkey'}; 
1623                             } else {
1624                                 daemon_log("$session_id ERROR: cannot find hostkey for '$host_name' in '$known_server_tn'", 1); 
1625                                 daemon_log("$session_id ERROR: unable to forward answer to correct registration server, processing is aborted!", 1); 
1626                                 $reg_server_key = undef;
1627                             }
1629                             # Send answer to server where client is registered
1630                             if (defined $reg_server_key) {
1631                                 $answer =~ s/$answer_target/$host_name/g;
1632                                 my $error = &send_msg_to_target($answer, $reg_server, $reg_server_key, $answer_header, $session_id);
1633                                 &update_jobdb_status_for_send_msgs($session_id, $answer, $error);
1634                                 $found_ip_flag++ ;
1635                             }
1636                         }
1637                     }
1639                     # No mac to ip matching found
1640                     if( $found_ip_flag == 0) {
1641                         daemon_log("$session_id WARNING: no host found in known_clients or foreign_clients with mac address '$answer_target'", 3);
1642                         &reactivate_job_with_delay($session_id, $answer_target, $answer_header, 30);
1643                     }
1645                 # Answer is for one specific host   
1646                 } else {
1647                     # get encrypt_key
1648                     my $encrypt_key = &get_encrypt_key($answer_target);
1649                     if( not defined $encrypt_key ) {
1650                         # unknown target
1651                         daemon_log("$session_id WARNING: unknown target '$answer_target'", 3);
1652                         next;
1653                     }
1654                     my $error = &send_msg_to_target($answer, $answer_target, $encrypt_key, $answer_header,$session_id);
1655                     &update_jobdb_status_for_send_msgs($session_id, $answer, $error);
1656                 }
1657             }
1658         }
1659     }
1661     my $filter = POE::Filter::Reference->new();
1662     my %result = ( 
1663             status => "seems ok to me",
1664             answer => $client_answer,
1665             );
1667     my $output = $filter->put( [ \%result ] );
1668     print @$output;
1673 sub session_start {
1674     my ($kernel) = $_[KERNEL];
1675     $global_kernel = $kernel;
1676     $kernel->yield('register_at_foreign_servers');
1677         $kernel->yield('create_fai_server_db', $fai_server_tn );
1678         $kernel->yield('create_fai_release_db', $fai_release_tn );
1679     $kernel->yield('watch_for_next_tasks');
1680         $kernel->sig(USR1 => "sig_handler");
1681         $kernel->sig(USR2 => "recreate_packages_db");
1682         $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1683         $kernel->delay_set('watch_for_done_jobs', $job_queue_loop_delay); 
1684     $kernel->delay_set('watch_for_modified_jobs', $modified_jobs_loop_delay); 
1685         $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay);
1686     $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay);
1687         $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay);
1688     $kernel->delay_set('watch_for_old_known_clients', $job_queue_loop_delay);
1690     # Start opsi check
1691     if ($opsi_enabled eq "true") {
1692         $kernel->delay_set('watch_for_opsi_jobs', $job_queue_opsi_delay); 
1693     }
1698 sub watch_for_done_jobs {
1699         #CHECK: $heap for what?
1700         my ($kernel,$heap) = @_[KERNEL, HEAP];
1702         my $sql_statement = "SELECT * FROM ".$job_queue_tn." WHERE ((status='done') AND (modified='0'))";
1703         my $res = $job_db->select_dbentry( $sql_statement );
1705         while( my ($id, $hit) = each %{$res} ) {
1706                 my $jobdb_id = $hit->{id};
1707                 my $sql_statement = "DELETE FROM $job_queue_tn WHERE id=$jobdb_id"; 
1708                 my $res = $job_db->del_dbentry($sql_statement); 
1709         }
1711         $kernel->delay_set('watch_for_done_jobs',$job_queue_loop_delay);
1715 sub watch_for_opsi_jobs {
1716     my ($kernel) = $_[KERNEL];
1718     # 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 
1719     # opsi install job is to parse the xml message. There is still the correct header.
1720     my $sql_statement = "SELECT * FROM ".$job_queue_tn." WHERE ((xmlmessage LIKE '%opsi_install_client</header>%') AND (status='processing') AND (siserver='localhost'))";
1721         my $res = $job_db->select_dbentry( $sql_statement );
1723     # Ask OPSI for an update of the running jobs
1724     while (my ($id, $hit) = each %$res ) {
1725         # Determine current parameters of the job
1726         my $hostId = $hit->{'plainname'};
1727         my $macaddress = $hit->{'macaddress'};
1728         my $progress = $hit->{'progress'};
1730         my $result= {};
1731         
1732         # For hosts, only return the products that are or get installed
1733         my $callobj;
1734         $callobj = {
1735             method  => 'getProductStates_hash',
1736             params  => [ $hostId ],
1737             id  => 1,
1738         };
1739         
1740         my $hres = $opsi_client->call($opsi_url, $callobj);
1741         #my ($hres_err, $hres_err_string) = &check_opsi_res($hres);
1742         if (not &check_opsi_res($hres)) {
1743             my $htmp= $hres->result->{$hostId};
1744         
1745             # Check state != not_installed or action == setup -> load and add
1746             my $products= 0;
1747             my $installed= 0;
1748             my $installing = 0;
1749             my $error= 0;  
1750             my @installed_list;
1751             my @error_list;
1752             my $act_status = "none";
1753             foreach my $product (@{$htmp}){
1755                 if ($product->{'installationStatus'} ne "not_installed" or
1756                         $product->{'actionRequest'} eq "setup"){
1758                     # Increase number of products for this host
1759                     $products++;
1760         
1761                     if ($product->{'installationStatus'} eq "failed"){
1762                         $result->{$product->{'productId'}}= "error";
1763                         unshift(@error_list, $product->{'productId'});
1764                         $error++;
1765                     }
1766                     if ($product->{'installationStatus'} eq "installed" && $product->{'actionRequest'}  eq "none"){
1767                         $result->{$product->{'productId'}}= "installed";
1768                         unshift(@installed_list, $product->{'productId'});
1769                         $installed++;
1770                     }
1771                     if ($product->{'installationStatus'} eq "installing"){
1772                         $result->{$product->{'productId'}}= "installing";
1773                         $installing++;
1774                         $act_status = "installing - ".$product->{'productId'};
1775                     }
1776                 }
1777             }
1778         
1779             # Estimate "rough" progress, avoid division by zero
1780             if ($products == 0) {
1781                 $result->{'progress'}= 0;
1782             } else {
1783                 $result->{'progress'}= int($installed * 100 / $products);
1784             }
1786             # Set updates in job queue
1787             if ((not $error) && (not $installing) && ($installed)) {
1788                 $act_status = "installed - ".join(", ", @installed_list);
1789             }
1790             if ($error) {
1791                 $act_status = "error - ".join(", ", @error_list);
1792             }
1793             if ($progress ne $result->{'progress'} ) {
1794                 # Updating progress and result 
1795                 my $update_statement = "UPDATE $job_queue_tn SET modified='1', progress='".$result->{'progress'}."', result='$act_status' WHERE macaddress LIKE '$macaddress' AND siserver='localhost'";
1796                 my $update_res = $job_db->update_dbentry($update_statement);
1797             }
1798             if ($progress eq 100) { 
1799                 # Updateing status
1800                 my $done_statement = "UPDATE $job_queue_tn SET modified='1', ";
1801                 if ($error) {
1802                     $done_statement .= "status='error'";
1803                 } else {
1804                     $done_statement .= "status='done'";
1805                 }
1806                 $done_statement .= " WHERE macaddress LIKE '$macaddress' AND siserver='localhost'";
1807                 my $done_res = $job_db->update_dbentry($done_statement);
1808             }
1811         }
1812     }
1814     $kernel->delay_set('watch_for_opsi_jobs', $job_queue_opsi_delay);
1818 # If a job got an update or was modified anyway, send to all other si-server an update message of this jobs.
1819 sub watch_for_modified_jobs {
1820     my ($kernel,$heap) = @_[KERNEL, HEAP];
1822     my $sql_statement = "SELECT * FROM $job_queue_tn WHERE (modified='1')"; 
1823     my $res = $job_db->select_dbentry( $sql_statement );
1824     
1825     # if db contains no jobs which should be update, do nothing
1826     if (keys %$res != 0) {
1828         if ($job_synchronization  eq "true") {
1829             # make out of the db result a gosa-si message   
1830             my $update_msg = &db_res2si_msg ($res, "foreign_job_updates", "KNOWN_SERVER", "MY_LOCAL_ADDRESS");
1831  
1832             # update all other SI-server
1833             &inform_all_other_si_server($update_msg);
1834         }
1836         # set jobs all jobs to modified = 0, wait until the next modification for updates of other si-server
1837         $sql_statement = "UPDATE $job_queue_tn SET modified='0' ";
1838         $res = $job_db->update_dbentry($sql_statement);
1839     }
1841     $kernel->delay_set('watch_for_modified_jobs', $modified_jobs_loop_delay);
1845 sub watch_for_new_jobs {
1846         if($watch_for_new_jobs_in_progress == 0) {
1847                 $watch_for_new_jobs_in_progress = 1;
1848                 my ($kernel,$heap) = @_[KERNEL, HEAP];
1850                 # check gosa job queue for jobs with executable timestamp
1851                 my $timestamp = &get_time();
1852                 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE siserver='localhost' AND status='waiting' AND (CAST(timestamp AS UNSIGNED)) < $timestamp ORDER BY timestamp";
1853                 my $res = $job_db->exec_statement( $sql_statement );
1855                 # Merge all new jobs that would do the same actions
1856                 my @drops;
1857                 my $hits;
1858                 foreach my $hit (reverse @{$res} ) {
1859                         my $macaddress= lc @{$hit}[8];
1860                         my $headertag= @{$hit}[5];
1861                         if(
1862                                 defined($hits->{$macaddress}) &&
1863                                 defined($hits->{$macaddress}->{$headertag}) &&
1864                                 defined($hits->{$macaddress}->{$headertag}[0])
1865                         ) {
1866                                 push @drops, "DELETE FROM $job_queue_tn WHERE id = $hits->{$macaddress}->{$headertag}[0]";
1867                         }
1868                         $hits->{$macaddress}->{$headertag}= $hit;
1869                 }
1871                 # Delete new jobs with a matching job in state 'processing'
1872                 foreach my $macaddress (keys %{$hits}) {
1873                         foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1874                                 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1875                                 if(defined($jobdb_id)) {
1876                                         my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND headertag='$jobdb_headertag' AND status='processing'";
1877                                         my $res = $job_db->exec_statement( $sql_statement );
1878                                         foreach my $hit (@{$res}) {
1879                                                 push @drops, "DELETE FROM $job_queue_tn WHERE id=$jobdb_id";
1880                                         }
1881                                 } else {
1882                                         daemon_log("J ERROR: Job without id exists for macaddress $macaddress!", 1);
1883                                 }
1884                         }
1885                 }
1887                 # Commit deletion
1888                 $job_db->exec_statementlist(\@drops);
1890                 # Look for new jobs that could be executed
1891                 foreach my $macaddress (keys %{$hits}) {
1893                         # Look if there is an executing job
1894                         my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND status='processing'";
1895                         my $res = $job_db->exec_statement( $sql_statement );
1897                         # Skip new jobs for host if there is a processing job
1898                         if(defined($res) and defined @{$res}[0]) {
1899                                 # Prevent race condition if there is a trigger_activate job waiting and a goto-activation job processing
1900                                 my $row = @{$res}[0] if (ref $res eq 'ARRAY');
1901                                 if(@{$row}[5] eq 'trigger_action_reinstall') {
1902                                         my $sql_statement_2 =  "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND status='waiting' AND headertag = 'trigger_activate_new'"; 
1903                                         my $res_2 = $job_db->exec_statement( $sql_statement_2 );
1904                                         if(defined($res_2) and defined @{$res_2}[0]) {
1905                                                 # Set status from goto-activation to 'waiting' and update timestamp
1906                                                 $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'");
1907                                         }
1908                                 }
1909                                 next;
1910                         }
1912                         foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1913                                 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1914                                 if(defined($jobdb_id)) {
1915                                         my $job_msg = @{$hits->{$macaddress}->{$jobdb_headertag}}[7];
1917                                         daemon_log("J DEBUG: its time to execute $job_msg", 7);
1918                                         my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$macaddress'";
1919                                         my $res_hash = $known_clients_db->select_dbentry( $sql_statement );
1921                                         # expect macaddress is unique!!!!!!
1922                                         my $target = $res_hash->{1}->{hostname};
1924                                         # change header
1925                                         $job_msg =~ s/<header>job_/<header>gosa_/;
1927                                         # add sqlite_id
1928                                         $job_msg =~ s/<\/xml>$/<jobdb_id>$jobdb_id<\/jobdb_id><\/xml>/;
1930                                         $job_msg =~ /<header>(\S+)<\/header>/;
1931                                         my $header = $1 ;
1932                                         my $func_error = &send_msg_to_target($job_msg, $server_address, $GosaPackages_key, $header, "J");                    
1934                                         # update status in job queue to ...
1935                     # ... 'processing', for jobs: 'reinstall', 'update'
1936                     if (($header =~ /gosa_trigger_action_reinstall/) 
1937                             || ($header =~ /gosa_trigger_activate_new/)
1938                             || ($header =~ /gosa_trigger_action_update/)) {
1939                         my $sql_statement = "UPDATE $job_queue_tn SET status='processing' WHERE id=$jobdb_id";
1940                         my $dbres = $job_db->update_dbentry($sql_statement);
1941                     }
1943                     # ... 'done', for all other jobs, they are no longer needed in the jobqueue
1944                     else {
1945                         my $sql_statement = "UPDATE $job_queue_tn SET status='done' WHERE id=$jobdb_id";
1946                         my $dbres = $job_db->update_dbentry($sql_statement);
1947                     }
1948                 
1950                                         # We don't want parallel processing
1951                                         last;
1952                                 }
1953                         }
1954                 }
1956                 $watch_for_new_jobs_in_progress = 0;
1957                 $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1958         }
1962 sub watch_for_new_messages {
1963     my ($kernel,$heap) = @_[KERNEL, HEAP];
1964     my @coll_user_msg;   # collection list of outgoing messages
1965     
1966     # check messaging_db for new incoming messages with executable timestamp
1967     my $timestamp = &get_time();
1968     my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( (CAST(timestamp AS UNSIGNED))<$timestamp AND flag='n' AND direction='in' )";
1969     my $res = $messaging_db->exec_statement( $sql_statement );
1970         foreach my $hit (@{$res}) {
1972         # create outgoing messages
1973         my $message_to = @{$hit}[3];
1974         # translate message_to to plain login name
1975         my @message_to_l = split(/,/, $message_to);  
1976                 my %receiver_h; 
1977                 foreach my $receiver (@message_to_l) {
1978                         if ($receiver =~ /^u_([\s\S]*)$/) {
1979                                 $receiver_h{$1} = 0;
1980                         } elsif ($receiver =~ /^g_([\s\S]*)$/) {
1981                                 my $group_name = $1;
1982                                 # fetch all group members from ldap and add them to receiver hash
1983                                 my $ldap_handle = &get_ldap_handle();
1984                                 if (defined $ldap_handle) {
1985                                                 my $mesg = $ldap_handle->search(
1986                                                                                 base => $ldap_base,
1987                                                                                 scope => 'sub',
1988                                                                                 attrs => ['memberUid'],
1989                                                                                 filter => "cn=$group_name",
1990                                                                                 );
1991                                                 if ($mesg->count) {
1992                                                                 my @entries = $mesg->entries;
1993                                                                 foreach my $entry (@entries) {
1994                                                                                 my @receivers= $entry->get_value("memberUid");
1995                                                                                 foreach my $receiver (@receivers) { 
1996                                                                                                 $receiver_h{$receiver} = 0;
1997                                                                                 }
1998                                                                 }
1999                                                 } 
2000                                                 # translating errors ?
2001                                                 if ($mesg->code) {
2002                                                                 daemon_log("M ERROR: unable to translate group '$group_name' to user list for message delivery: $mesg->error", 1);
2003                                                 }
2004                                                 &release_ldap_handle($ldap_handle);
2005                                 # ldap handle error ?           
2006                                 } else {
2007                                         daemon_log("M ERROR: unable to translate group '$group_name' to user list for message delivery: no ldap handle available", 1);
2008                                 }
2009                         } else {
2010                                 my $sbjct = &encode_base64(@{$hit}[1]);
2011                                 my $msg = &encode_base64(@{$hit}[7]);
2012                                 &daemon_log("M WARNING: unknown receiver '$receiver' for a user-message '$sbjct - $msg'", 3); 
2013                         }
2014                 }
2015                 my @receiver_l = keys(%receiver_h);
2017         my $message_id = @{$hit}[0];
2019         #add each outgoing msg to messaging_db
2020         my $receiver;
2021         foreach $receiver (@receiver_l) {
2022             my $sql_statement = "INSERT INTO $messaging_tn (id, subject, message_from, message_to, flag, direction, delivery_time, message, timestamp) ".
2023                 "VALUES ('".
2024                 $message_id."', '".    # id
2025                 @{$hit}[1]."', '".     # subject
2026                 @{$hit}[2]."', '".     # message_from
2027                 $receiver."', '".      # message_to
2028                 "none"."', '".         # flag
2029                 "out"."', '".          # direction
2030                 @{$hit}[6]."', '".     # delivery_time
2031                 @{$hit}[7]."', '".     # message
2032                 $timestamp."'".     # timestamp
2033                 ")";
2034             &daemon_log("M DEBUG: $sql_statement", 1);
2035             my $res = $messaging_db->exec_statement($sql_statement);
2036             &daemon_log("M INFO: message '".@{$hit}[0]."' is prepared for delivery to receiver '$receiver'", 5);
2037         }
2039         # set incoming message to flag d=deliverd
2040         $sql_statement = "UPDATE $messaging_tn SET flag='p' WHERE id='$message_id'"; 
2041         &daemon_log("M DEBUG: $sql_statement", 7);
2042         $res = $messaging_db->update_dbentry($sql_statement);
2043         &daemon_log("M INFO: message '$message_id' is set to flag 'p' (processed)", 5);
2044     }
2046     $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay); 
2047     return;
2050 sub watch_for_delivery_messages {
2051     my ($kernel, $heap) = @_[KERNEL, HEAP];
2053     # select outgoing messages
2054     my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( flag='p' AND direction='out' )";
2055     my $res = $messaging_db->exec_statement( $sql_statement );
2056     
2057     # build out msg for each    usr
2058     foreach my $hit (@{$res}) {
2059         my $receiver = @{$hit}[3];
2060         my $msg_id = @{$hit}[0];
2061         my $subject = @{$hit}[1];
2062         my $message = @{$hit}[7];
2064         # resolve usr -> host where usr is logged in
2065         my $sql = "SELECT * FROM $login_users_tn WHERE (user='$receiver')"; 
2066         my $res = $login_users_db->exec_statement($sql);
2068         # receiver is logged in nowhere
2069         if (not ref(@$res[0]) eq "ARRAY") { next; }    
2071         # receiver ist logged in at a client registered at local server
2072                 my $send_succeed = 0;
2073                 foreach my $hit (@$res) {
2074                                 my $receiver_host = @$hit[0];
2075                 my $delivered2host = 0;
2076                                 &daemon_log("M DEBUG: user '$receiver' is logged in at host '$receiver_host'", 7);
2078                                 # Looking for host in know_clients_db 
2079                                 my $sql = "SELECT * FROM $known_clients_tn WHERE (hostname='$receiver_host')";
2080                                 my $res = $known_clients_db->exec_statement($sql);
2082                 # Host is known in known_clients_db
2083                 if (ref(@$res[0]) eq "ARRAY") {
2084                     my $receiver_key = @{@{$res}[0]}[2];
2085                     my %data = ('subject' => $subject, 'message' => $message, 'usr' => $receiver);
2086                     my $out_msg = &build_msg("usr_msg", $server_address, $receiver_host, \%data ); 
2087                     my $error = &send_msg_to_target($out_msg, $receiver_host, $receiver_key, "usr_msg", 0); 
2088                     if ($error == 0 ) {
2089                         $send_succeed++ ;
2090                         $delivered2host++ ;
2091                         &daemon_log("M DEBUG: send message for user '$receiver' to host '$receiver_host'", 7); 
2092                     } else {
2093                         &daemon_log("M DEBUG: cannot send message for user '$receiver' to host '$receiver_host'", 7); 
2094                     }
2095                 }
2096                 
2097                 # Message already send, do not need to do anything more, otherwise ...
2098                 if ($delivered2host) { next;}
2099     
2100                 # ...looking for host in foreign_clients_db
2101                 $sql = "SELECT * FROM $foreign_clients_tn WHERE (hostname='$receiver_host')";
2102                 $res = $foreign_clients_db->exec_statement($sql);
2103   
2104                                 # Host is known in foreign_clients_db 
2105                                 if (ref(@$res[0]) eq "ARRAY") { 
2106                     my $registration_server = @{@{$res}[0]}[2];
2107                     
2108                     # Fetch encryption key for registration server
2109                     my $sql = "SELECT * FROM $known_server_tn WHERE (hostname='$registration_server')";
2110                     my $res = $known_server_db->exec_statement($sql);
2111                     if (ref(@$res[0]) eq "ARRAY") { 
2112                         my $registration_server_key = @{@{$res}[0]}[3];
2113                         my %data = ('subject' => $subject, 'message' => $message, 'usr' => $receiver);
2114                         my $out_msg = &build_msg("usr_msg", $server_address, $receiver_host, \%data ); 
2115                         my $error = &send_msg_to_target($out_msg, $registration_server, $registration_server_key, "usr_msg", 0); 
2116                         if ($error == 0 ) {
2117                             $send_succeed++ ;
2118                             $delivered2host++ ;
2119                             &daemon_log("M DEBUG: send message for user '$receiver' to server '$registration_server'", 7); 
2120                         } else {
2121                             &daemon_log("M ERROR: cannot send message for user '$receiver' to server '$registration_server'", 1); 
2122                         }
2124                     } else {
2125                         &daemon_log("M ERROR: host '$receiver_host' is reported to be ".
2126                                 "registrated at server '$registration_server', ".
2127                                 "but no data available in known_server_db ", 1); 
2128                     }
2129                 }
2130                 
2131                 if (not $delivered2host) {
2132                     &daemon_log("M ERROR: unable to send user message to host '$receiver_host'", 1);
2133                 }
2134                 }
2136                 if ($send_succeed) {
2137                                 # set outgoing msg at db to deliverd
2138                                 my $sql = "UPDATE $messaging_tn SET flag='d' WHERE (id='$msg_id' AND direction='out' AND message_to='$receiver')"; 
2139                                 my $res = $messaging_db->exec_statement($sql); 
2140                 &daemon_log("M INFO: send message for user '$receiver' to logged in hosts", 5);
2141                 } else {
2142             &daemon_log("M WARNING: failed to deliver message for user '$receiver'", 3); 
2143         }
2144         }
2146     $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay); 
2147     return;
2151 sub watch_for_done_messages {
2152     my ($kernel,$heap) = @_[KERNEL, HEAP];
2154     my $sql = "SELECT * FROM $messaging_tn WHERE (flag='p' AND direction='in')"; 
2155     my $res = $messaging_db->exec_statement($sql); 
2157     foreach my $hit (@{$res}) {
2158         my $msg_id = @{$hit}[0];
2160         my $sql = "SELECT * FROM $messaging_tn WHERE (id='$msg_id' AND direction='out' AND (NOT flag='s'))"; 
2161         my $res = $messaging_db->exec_statement($sql);
2163         # not all usr msgs have been seen till now
2164         if ( ref(@$res[0]) eq "ARRAY") { next; }
2165         
2166         $sql = "DELETE FROM $messaging_tn WHERE (id='$msg_id')"; 
2167         $res = $messaging_db->exec_statement($sql);
2168     
2169     }
2171     $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay); 
2172     return;
2176 sub watch_for_old_known_clients {
2177     my ($kernel,$heap) = @_[KERNEL, HEAP];
2179     my $sql_statement = "SELECT * FROM $known_clients_tn";
2180     my $res = $known_clients_db->select_dbentry( $sql_statement );
2182     my $cur_time = int(&get_time());
2184     while ( my ($hit_num, $hit) = each %$res) {
2185         my $expired_timestamp = int($hit->{'timestamp'});
2186         $expired_timestamp =~ /(\d{4})(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)/;
2187         my $dt = DateTime->new( year   => $1,
2188                 month  => $2,
2189                 day    => $3,
2190                 hour   => $4,
2191                 minute => $5,
2192                 second => $6,
2193                 );
2195         $dt->add( seconds => 2 * int($hit->{'keylifetime'}) );
2196         $expired_timestamp = $dt->ymd('').$dt->hms('');
2197         if ($cur_time > $expired_timestamp) {
2198             my $hostname = $hit->{'hostname'};
2199             my $del_sql = "DELETE FROM $known_clients_tn WHERE hostname='$hostname'"; 
2200             my $del_res = $known_clients_db->exec_statement($del_sql);
2202             &main::daemon_log("0 INFO: timestamp '".$hit->{'timestamp'}."' of client '$hostname' is expired('$expired_timestamp'), client will be deleted from known_clients_db", 5);
2203         }
2205     }
2207     $kernel->delay_set('watch_for_old_known_clients', $job_queue_loop_delay);
2211 sub watch_for_next_tasks {
2212     my ($kernel,$heap) = @_[KERNEL, HEAP];
2214     my $sql = "SELECT * FROM $incoming_tn";
2215     my $res = $incoming_db->select_dbentry($sql);
2216     
2217     while ( my ($hit_num, $hit) = each %$res) {
2218         my $headertag = $hit->{'headertag'};
2219         if ($headertag =~ /^answer_(\d+)/) {
2220             # do not start processing, this message is for a still running POE::Wheel
2221             next;
2222         }
2223         my $message_id = $hit->{'id'};
2224         my $session_id = $hit->{'sessionid'};
2225         &daemon_log("$session_id DEBUG: start processing for message with incoming id: '$message_id'", 11);
2227         $kernel->yield('next_task', $hit);
2229         my $sql = "DELETE FROM $incoming_tn WHERE id=$message_id";
2230         my $res = $incoming_db->exec_statement($sql);
2231     }
2233     $kernel->delay_set('watch_for_next_tasks', 1); 
2237 sub get_ldap_handle {
2238         my ($session_id) = @_;
2239         my $heap;
2241         if (not defined $session_id ) { $session_id = 0 };
2242         if ($session_id =~ /[^0-9]*/) { $session_id = 0 };
2244         my ($package, $file, $row, $subroutine, $hasArgs, $wantArray, $evalText, $isRequire) = caller(1);
2245         my $caller_text = "subroutine $subroutine";
2246         if ($subroutine eq "(eval)") {
2247                 $caller_text = "eval block within file '$file' for '$evalText'"; 
2248         }
2249         daemon_log("$session_id DEBUG: new ldap handle for '$caller_text' required!", 42);
2251 get_handle:
2252         my $ldap_handle = Net::LDAP->new( $ldap_uri );
2253         if (not ref $ldap_handle) {
2254                 daemon_log("$session_id ERROR: Connection to LDAP URI '$ldap_uri' failed! Retrying!", 1);
2255                 usleep(100000);
2256                 goto get_handle;
2257         } else {
2258                 daemon_log("$session_id DEBUG: Connection to LDAP URI '$ldap_uri' established.", 42);
2259         }
2261         $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);
2262         return $ldap_handle;
2266 sub release_ldap_handle {
2267         my ($ldap_handle, $session_id) = @_ ;
2268         if (not defined $session_id ) { $session_id = 0 };
2270         if(ref $ldap_handle) {
2271           $ldap_handle->disconnect();
2272   }
2273         &main::daemon_log("$session_id DEBUG: Released a ldap handle!", 42);
2274         return;
2278 sub change_fai_state {
2279         my ($st, $targets, $session_id) = @_;
2280         $session_id = 0 if not defined $session_id;
2281         # Set FAI state to localboot
2282         my %mapActions= (
2283                 reboot    => '',
2284                 update    => 'softupdate',
2285                 localboot => 'localboot',
2286                 reinstall => 'install',
2287                 rescan    => '',
2288                 wake      => '',
2289                 memcheck  => 'memcheck',
2290                 sysinfo   => 'sysinfo',
2291                 install   => 'install',
2292         );
2294         # Return if this is unknown
2295         if (!exists $mapActions{ $st }){
2296                 daemon_log("$session_id ERROR: unknown action '$st', can not translate ot FAIstate", 1); 
2297                 return;
2298         }
2300         my $state= $mapActions{ $st };
2302         # Build search filter for hosts
2303         my $search= "(&(objectClass=GOhard)";
2304         foreach (@{$targets}){
2305                 $search.= "(macAddress=$_)";
2306         }
2307         $search.= ")";
2309         # If there's any host inside of the search string, procress them
2310         if (!($search =~ /macAddress/)){
2311                 daemon_log("$session_id ERROR: no macAddress found in filter statement for LDAP search: '$search'", 1);    
2312                 return;
2313         }
2315         my $ldap_handle = &get_ldap_handle($session_id);
2316         # Perform search for Unit Tag
2317         my $mesg = $ldap_handle->search(
2318                 base   => $ldap_base,
2319                 scope  => 'sub',
2320                 attrs  => ['dn', 'FAIstate', 'objectClass'],
2321                 filter => "$search"
2322         );
2324         if ($mesg->count) {
2325                 my @entries = $mesg->entries;
2326                 if (0 == @entries) {
2327                         daemon_log("$session_id ERROR: ldap search failed: ldap_base=$ldap_base, filter=$search", 1); 
2328                 }
2330                 foreach my $entry (@entries) {
2331                         # Only modify entry if it is not set to '$state'
2332                         if ($entry->get_value("FAIstate") ne "$state"){
2333                                 daemon_log("$session_id INFO: Setting FAIstate to '$state' for ".$entry->dn, 5);
2334                                 my $result;
2335                                 my %tmp = map { $_ => 1 } $entry->get_value("objectClass");
2336                                 if (exists $tmp{'FAIobject'}){
2337                                         if ($state eq ''){
2338                                                 $result= $ldap_handle->modify($entry->dn, changes => [ delete => [ FAIstate => [] ] ]);
2339                                         } else {
2340                                                 $result= $ldap_handle->modify($entry->dn, changes => [ replace => [ FAIstate => $state ] ]);
2341                                         }
2342                                 } elsif ($state ne ''){
2343                                         $result= $ldap_handle->modify($entry->dn, changes => [ add => [ objectClass => 'FAIobject' ], add => [ FAIstate => $state ] ]);
2344                                 }
2346                                 # Errors?
2347                                 if ($result->code){
2348                                         daemon_log("$session_id Error: Setting FAIstate to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
2349                                 }
2350                         } else {
2351                                 daemon_log("$session_id DEBUG FAIstate at host '".$entry->dn."' already at state '$st'", 42); 
2352                         }  
2353                 }
2354         } else {
2355                 daemon_log("$session_id ERROR: LDAP search failed: ldap_base=$ldap_base, filter=$search", 1);
2356         }
2357         &release_ldap_handle($ldap_handle, $session_id);                  
2359         return;
2363 sub change_goto_state {
2364     my ($st, $targets, $session_id) = @_;
2365     $session_id = 0  if not defined $session_id;
2367     # Switch on or off?
2368     my $state= $st eq 'active' ? 'active': 'locked';
2370     my $ldap_handle = &get_ldap_handle($session_id);
2371     if( defined($ldap_handle) ) {
2373       # Build search filter for hosts
2374       my $search= "(&(objectClass=GOhard)";
2375       foreach (@{$targets}){
2376         $search.= "(macAddress=$_)";
2377       }
2378       $search.= ")";
2380       # If there's any host inside of the search string, procress them
2381       if (!($search =~ /macAddress/)){
2382               &release_ldap_handle($ldap_handle);
2383         return;
2384       }
2386       # Perform search for Unit Tag
2387       my $mesg = $ldap_handle->search(
2388           base   => $ldap_base,
2389           scope  => 'sub',
2390           attrs  => ['dn', 'gotoMode'],
2391           filter => "$search"
2392           );
2394       if ($mesg->count) {
2395         my @entries = $mesg->entries;
2396         foreach my $entry (@entries) {
2398           # Only modify entry if it is not set to '$state'
2399           if ($entry->get_value("gotoMode") ne $state){
2401             daemon_log("$session_id INFO: Setting gotoMode to '$state' for ".$entry->dn, 5);
2402             my $result;
2403             $result= $ldap_handle->modify($entry->dn, changes => [replace => [ gotoMode => $state ] ]);
2405             # Errors?
2406             if ($result->code){
2407               &daemon_log("$session_id Error: Setting gotoMode to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
2408             }
2410           }
2411         }
2412       } else {
2413                 daemon_log("$session_id ERROR: LDAP search failed in function change_goto_state: ldap_base=$ldap_base, filter=$search", 1);
2414           }
2416     }
2417         &release_ldap_handle($ldap_handle, $session_id);
2418         return;
2422 sub run_recreate_packages_db {
2423     my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
2424     my $session_id = $session->ID;
2425         &main::daemon_log("$session_id INFO: Recreating FAI Packages DB ('$fai_release_tn', '$fai_server_tn', '$packages_list_tn')", 5);
2426         $kernel->yield('create_fai_release_db', $fai_release_tn);
2427         $kernel->yield('create_fai_server_db', $fai_server_tn);
2428         return;
2432 sub run_create_fai_server_db {
2433     my ($kernel, $session, $heap, $table_name) = @_[KERNEL, SESSION, HEAP, ARG0];
2434     my $session_id = $session->ID;
2435     my $task = POE::Wheel::Run->new(
2436             Program => sub { &create_fai_server_db($table_name,$kernel, undef, $session_id) },
2437             StdoutEvent  => "session_run_result",
2438             StderrEvent  => "session_run_debug",
2439             CloseEvent   => "session_run_done",
2440             );
2442     $heap->{task}->{ $task->ID } = $task;
2443     return;
2447 sub create_fai_server_db {
2448         my ($table_name, $kernel, $dont_create_packages_list, $session_id) = @_;
2449         my $result;
2451         if (not defined $session_id) { $session_id = 0; }
2452         my $ldap_handle = &get_ldap_handle($session_id);
2453         if(defined($ldap_handle)) {
2454                 daemon_log("$session_id INFO: create_fai_server_db: start", 5);
2455                 my $mesg= $ldap_handle->search(
2456                         base   => $ldap_base,
2457                         scope  => 'sub',
2458                         attrs  => ['FAIrepository', 'gosaUnitTag'],
2459                         filter => "(&(FAIrepository=*)(objectClass=FAIrepositoryServer))",
2460                 );
2461                 if($mesg->{'resultCode'} == 0 &&
2462                         $mesg->count != 0) {
2463                         foreach my $entry (@{$mesg->{entries}}) {
2464                                 if($entry->exists('FAIrepository')) {
2465                                         # Add an entry for each Repository configured for server
2466                                         foreach my $repo(@{$entry->get_value('FAIrepository', asref => 1)}) {
2467                                                 my($tmp_url,$tmp_server,$tmp_release,$tmp_sections) = split(/\|/, $repo);
2468                                                 my $tmp_tag= $entry->get_value('gosaUnitTag') || "";
2469                                                 $result= $fai_server_db->add_dbentry( { 
2470                                                                 table => $table_name,
2471                                                                 primkey => ['server', 'fai_release', 'tag'],
2472                                                                 server => $tmp_url,
2473                                                                 fai_release => $tmp_release,
2474                                                                 sections => $tmp_sections,
2475                                                                 tag => (length($tmp_tag)>0)?$tmp_tag:"",
2476                                                         } );
2477                                         }
2478                                 }
2479                         }
2480                 }
2481                 daemon_log("$session_id INFO: create_fai_server_db: finished", 5);
2482                 &release_ldap_handle($ldap_handle);
2484                 # TODO: Find a way to post the 'create_packages_list_db' event
2485                 if(not defined($dont_create_packages_list)) {
2486                         &create_packages_list_db(undef, $session_id);
2487                 }
2488         }       
2490         return $result;
2494 sub run_create_fai_release_db {
2495         my ($kernel, $session, $heap, $table_name) = @_[KERNEL, SESSION, HEAP, ARG0];
2496         my $session_id = $session->ID;
2497         my $task = POE::Wheel::Run->new(
2498                 Program => sub { &create_fai_release_db($table_name, $session_id) },
2499                 StdoutEvent  => "session_run_result",
2500                 StderrEvent  => "session_run_debug",
2501                 CloseEvent   => "session_run_done",
2502         );
2504         $heap->{task}->{ $task->ID } = $task;
2505         return;
2509 sub create_fai_release_db {
2510         my ($table_name, $session_id) = @_;
2511         my $result;
2513         # used for logging
2514         if (not defined $session_id) { $session_id = 0; }
2516         my $ldap_handle = &get_ldap_handle($session_id);
2517         if(defined($ldap_handle)) {
2518                 daemon_log("$session_id INFO: create_fai_release_db: start",5);
2519                 my $mesg= $ldap_handle->search(
2520                         base   => $ldap_base,
2521                         scope  => 'sub',
2522                         attrs  => [],
2523                         filter => "(&(objectClass=organizationalUnit)(ou=fai))",
2524                 );
2525                 if(($mesg->code == 0) && ($mesg->count != 0))
2526                 {
2527                         daemon_log("$session_id DEBUG: create_fai_release_db: count " . $mesg->count,138);
2529                         # Walk through all possible FAI container ou's
2530                         my @sql_list;
2531                         my $timestamp= &get_time();
2532                         foreach my $ou (@{$mesg->{entries}}) {
2533                                 my $tmp_classes= resolve_fai_classes($ou->dn, $ldap_handle, $session_id);
2534                                 if(defined($tmp_classes) && ref($tmp_classes) eq 'HASH') {
2535                                         my @tmp_array=get_fai_release_entries($tmp_classes);
2536                                         if(@tmp_array) {
2537                                                 foreach my $entry (@tmp_array) {
2538                                                         if(defined($entry) && ref($entry) eq 'HASH') {
2539                                                                 my $sql= 
2540                                                                 "INSERT INTO $table_name "
2541                                                                 ."(timestamp, fai_release, class, type, state) VALUES ("
2542                                                                 .$timestamp.","
2543                                                                 ."'".$entry->{'release'}."',"
2544                                                                 ."'".$entry->{'class'}."',"
2545                                                                 ."'".$entry->{'type'}."',"
2546                                                                 ."'".$entry->{'state'}."')";
2547                                                                 push @sql_list, $sql;
2548                                                         }
2549                                                 }
2550                                         }
2551                                 }
2552                         }
2554                         daemon_log("$session_id DEBUG: create_fai_release_db: Inserting ".scalar @sql_list." entries to DB",138);
2555             &release_ldap_handle($ldap_handle);
2556                         if(@sql_list) {
2557                                 unshift @sql_list, "VACUUM";
2558                                 unshift @sql_list, "DELETE FROM $table_name";
2559                                 $fai_release_db->exec_statementlist(\@sql_list);
2560                         }
2561                         daemon_log("$session_id DEBUG: create_fai_release_db: Done with inserting",138);
2562                 } else {
2563                         daemon_log("$session_id INFO: create_fai_release_db: error: " . $mesg->code, 5);
2564                 }
2565                 daemon_log("$session_id INFO: create_fai_release_db: finished",5);
2566         }
2567         return $result;
2570 sub get_fai_types {
2571         my $tmp_classes = shift || return undef;
2572         my @result;
2574         foreach my $type(keys %{$tmp_classes}) {
2575                 if(defined($tmp_classes->{$type}[0]) && (!($tmp_classes->{$type}[0] =~ /^.*?removed.*?$/))) {
2576                         my $entry = {
2577                                 type => $type,
2578                                 state => $tmp_classes->{$type}[0],
2579                         };
2580                         push @result, $entry;
2581                 }
2582         }
2584         return @result;
2587 sub get_fai_state {
2588         my $result = "";
2589         my $tmp_classes = shift || return $result;
2591         foreach my $type(keys %{$tmp_classes}) {
2592                 if(defined($tmp_classes->{$type}[0])) {
2593                         $result = $tmp_classes->{$type}[0];
2594                         
2595                 # State is equal for all types in class
2596                         last;
2597                 }
2598         }
2600         return $result;
2603 sub resolve_fai_classes {
2604         my ($fai_base, $ldap_handle, $session_id) = @_;
2605         if (not defined $session_id) { $session_id = 0; }
2606         my $result;
2607         my @possible_fai_classes= ("FAIscript", "FAIhook", "FAIpartitionTable", "FAItemplate", "FAIvariable", "FAIprofile", "FAIpackageList");
2608         my $fai_filter= "(|(&(objectClass=FAIclass)(|(objectClass=".join(")(objectClass=", @possible_fai_classes).")))(objectClass=FAIbranch))";
2609         my $fai_classes;
2611         daemon_log("$session_id DEBUG: Searching for FAI entries in base $fai_base", 138);
2612         my $mesg= $ldap_handle->search(
2613                 base   => $fai_base,
2614                 scope  => 'sub',
2615                 attrs  => ['cn','objectClass','FAIstate'],
2616                 filter => $fai_filter,
2617         );
2618         daemon_log("$session_id DEBUG: Found ".$mesg->count()." FAI entries", 138);
2620         if($mesg->{'resultCode'} == 0 &&
2621                 $mesg->count != 0) {
2622                 foreach my $entry (@{$mesg->{entries}}) {
2623                         if($entry->exists('cn')) {
2624                                 my $tmp_dn= $entry->dn();
2625                                 $tmp_dn= substr( $tmp_dn, 0, length($tmp_dn)
2626                                         - length($fai_base) - 1 );
2628                                 # Skip classname and ou dn parts for class
2629                                 my $tmp_release = ($1) if $tmp_dn =~ /^[^,]+,[^,]+,(.*?)$/;
2631                                 # Skip classes without releases
2632                                 if((!defined($tmp_release)) || length($tmp_release)==0) {
2633                                         next;
2634                                 }
2636                                 my $tmp_cn= $entry->get_value('cn');
2637                                 my $tmp_state= $entry->get_value('FAIstate');
2639                                 my $tmp_type;
2640                                 # Get FAI type
2641                                 for my $oclass(@{$entry->get_value('objectClass', asref => 1)}) {
2642                                         if(grep $_ eq $oclass, @possible_fai_classes) {
2643                                                 $tmp_type= $oclass;
2644                                                 last;
2645                                         }
2646                                 }
2648                                 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
2649                                         # A Subrelease
2650                                         my @sub_releases = split(/,/, $tmp_release);
2652                                         # Walk through subreleases and build hash tree
2653                                         my $hash;
2654                                         while(my $tmp_sub_release = pop @sub_releases) {
2655                                                 $hash .= "\{'$tmp_sub_release'\}->";                                            
2656                                         }
2657                                         eval('push @{$fai_classes->'.$hash.'{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";');
2658                                 } else {
2659                                         # A branch, no subrelease
2660                                         push @{$fai_classes->{$tmp_release}->{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";
2661                                 }
2662                         } elsif (!$entry->exists('cn')) {
2663                                 my $tmp_dn= $entry->dn();
2664                                 $tmp_dn= substr( $tmp_dn, 0, length($tmp_dn)
2665                                         - length($fai_base) - 1 );
2666                                 my $tmp_release = ($1) if $tmp_dn =~ /^(.*?)$/;
2668                                 # Skip classes without releases
2669                                 if((!defined($tmp_release)) || length($tmp_release)==0) {
2670                                         next;
2671                                 }
2673                                 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
2674                                         # A Subrelease
2675                                         my @sub_releases= split(/,/, $tmp_release);
2677                                         # Walk through subreleases and build hash tree
2678                                         my $hash;
2679                                         while(my $tmp_sub_release = pop @sub_releases) {
2680                                                 $hash .= "\{'$tmp_sub_release'\}->";                                            
2681                                         }
2682                                         # Remove the last two characters
2683                                         chop($hash);
2684                                         chop($hash);
2686                                         eval('$fai_classes->'.$hash.'= {}');
2687                                 } else {
2688                                         # A branch, no subrelease
2689                                         if(!exists($fai_classes->{$tmp_release})) {
2690                                                 $fai_classes->{$tmp_release} = {};
2691                                         }
2692                                 }
2693                         }
2694                 }
2696                 # The hash is complete, now we can honor the copy-on-write based missing entries
2697                 foreach my $release (keys %$fai_classes) {
2698                         $result->{$release}= deep_copy(apply_fai_inheritance($fai_classes->{$release}));
2699                 }
2700         }
2701         return $result;
2704 sub apply_fai_inheritance {
2705        my $fai_classes = shift || return {};
2706        my $tmp_classes;
2708        # Get the classes from the branch
2709        foreach my $class (keys %{$fai_classes}) {
2710                # Skip subreleases
2711                if($class =~ /^ou=.*$/) {
2712                        next;
2713                } else {
2714                        $tmp_classes->{$class}= deep_copy($fai_classes->{$class});
2715                }
2716        }
2718        # Apply to each subrelease
2719        foreach my $subrelease (keys %{$fai_classes}) {
2720                if($subrelease =~ /ou=/) {
2721                        foreach my $tmp_class (keys %{$tmp_classes}) {
2722                                if(!exists($fai_classes->{$subrelease}->{$tmp_class})) {
2723                                        $fai_classes->{$subrelease}->{$tmp_class} =
2724                                        deep_copy($tmp_classes->{$tmp_class});
2725                                } else {
2726                                        foreach my $type (keys %{$tmp_classes->{$tmp_class}}) {
2727                                                if(!exists($fai_classes->{$subrelease}->{$tmp_class}->{$type})) {
2728                                                        $fai_classes->{$subrelease}->{$tmp_class}->{$type}=
2729                                                        deep_copy($tmp_classes->{$tmp_class}->{$type});
2730                                                }
2731                                        }
2732                                }
2733                        }
2734                }
2735        }
2737        # Find subreleases in deeper levels
2738        foreach my $subrelease (keys %{$fai_classes}) {
2739                if($subrelease =~ /ou=/) {
2740                        foreach my $subsubrelease (keys %{$fai_classes->{$subrelease}}) {
2741                                if($subsubrelease =~ /ou=/) {
2742                                        apply_fai_inheritance($fai_classes->{$subrelease});
2743                                }
2744                        }
2745                }
2746        }
2748        return $fai_classes;
2751 sub get_fai_release_entries {
2752         my $tmp_classes = shift || return;
2753         my $parent = shift || "";
2754         my @result = shift || ();
2756         foreach my $entry (keys %{$tmp_classes}) {
2757                 if(defined($entry)) {
2758                         if($entry =~ /^ou=.*$/) {
2759                                 my $release_name = $entry;
2760                                 $release_name =~ s/ou=//g;
2761                                 if(length($parent)>0) {
2762                                         $release_name = $parent."/".$release_name;
2763                                 }
2764                                 my @bufentries = get_fai_release_entries($tmp_classes->{$entry}, $release_name, @result);
2765                                 foreach my $bufentry(@bufentries) {
2766                                         push @result, $bufentry;
2767                                 }
2768                         } else {
2769                                 my @types = get_fai_types($tmp_classes->{$entry});
2770                                 foreach my $type (@types) {
2771                                         push @result, 
2772                                         {
2773                                                 'class' => $entry,
2774                                                 'type' => $type->{'type'},
2775                                                 'release' => $parent,
2776                                                 'state' => $type->{'state'},
2777                                         };
2778                                 }
2779                         }
2780                 }
2781         }
2783         return @result;
2786 sub deep_copy {
2787         my $this = shift;
2788         if (not ref $this) {
2789                 $this;
2790         } elsif (ref $this eq "ARRAY") {
2791                 [map deep_copy($_), @$this];
2792         } elsif (ref $this eq "HASH") {
2793                 +{map { $_ => deep_copy($this->{$_}) } keys %$this};
2794         } else { die "what type is $_?" }
2798 sub session_run_result {
2799     my ($kernel, $heap, $client_answer) = @_[KERNEL, HEAP, ARG0];    
2800     $kernel->sig(CHLD => "child_reap");
2803 sub session_run_debug {
2804     my $result = $_[ARG0];
2805     print STDERR "$result\n";
2808 sub session_run_done {
2809     my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
2810     delete $heap->{task}->{$task_id};
2811         if (exists $heap->{ldap_handle}->{$task_id}) {
2812                 &release_ldap_handle($heap->{ldap_handle}->{$task_id});
2813         }
2814         delete $heap->{ldap_handle}->{$task_id};
2818 sub create_sources_list {
2819         my $session_id = shift || 0;
2820         my $result="/tmp/gosa_si_tmp_sources_list";
2822         # Remove old file
2823         if(stat($result)) {
2824                 unlink($result);
2825                 &main::daemon_log("$session_id DEBUG: remove an old version of '$result'", 7); 
2826         }
2828         my $fh;
2829         open($fh, ">$result");
2830         if (not defined $fh) {
2831                 &main::daemon_log("$session_id DEBUG: cannot open '$result' for writing", 7); 
2832                 return undef;
2833         }
2834         if(defined($main::ldap_server_dn) and length($main::ldap_server_dn) > 0) {
2835                 my $ldap_handle = &get_ldap_handle($session_id);
2836                 my $mesg=$ldap_handle->search(
2837                         base    => $main::ldap_server_dn,
2838                         scope   => 'base',
2839                         attrs   => 'FAIrepository',
2840                         filter  => 'objectClass=FAIrepositoryServer'
2841                 );
2842                 if($mesg->count) {
2843                         foreach my $entry(@{$mesg->{'entries'}}) {
2844                                 foreach my $value(@{$entry->get_value('FAIrepository', asref => 1)}) {
2845                                         my ($server, $tag, $release, $sections)= split /\|/, $value;
2846                                         my $line = "deb $server $release";
2847                                         $sections =~ s/,/ /g;
2848                                         $line.= " $sections";
2849                                         print $fh $line."\n";
2850                                 }
2851                         }
2852                 }
2853                 &release_ldap_handle($ldap_handle);
2854         } else {
2855                 if (defined $main::ldap_server_dn){
2856                         &main::daemon_log("$session_id ERROR: something wrong with ldap_server_dn '$main::ldap_server_dn', abort create_sources_list", 1); 
2857                 } else {
2858                         &main::daemon_log("$session_id ERROR: no ldap_server_dn found, abort create_sources_list", 1);
2859                 }
2860         }
2861         close($fh);
2863         return $result;
2867 sub run_create_packages_list_db {
2868     my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
2869         my $session_id = $session->ID;
2870         my $task = POE::Wheel::Run->new(
2871                                         Priority => +20,
2872                                         Program => sub {&create_packages_list_db(undef, $session_id)},
2873                                         StdoutEvent  => "session_run_result",
2874                                         StderrEvent  => "session_run_debug",
2875                                         CloseEvent   => "session_run_done",
2876                                         );
2877         $heap->{task}->{ $task->ID } = $task;
2881 sub create_packages_list_db {
2882         my ($sources_file, $session_id) = @_;
2883         
2884         # it should not be possible to trigger a recreation of packages_list_db
2885         # while packages_list_db is under construction, so set flag packages_list_under_construction
2886         # which is tested befor recreation can be started
2887         if (-r $packages_list_under_construction) {
2888                 daemon_log("$session_id WARNING: packages_list_db is right now under construction, please wait until this process is finished", 3);
2889                 return;
2890         } else {
2891                 daemon_log("$session_id INFO: create_packages_list_db: start", 5); 
2892                 # set packages_list_under_construction to true
2893                 system("touch $packages_list_under_construction");
2894                 @packages_list_statements=();
2895         }
2897         if (not defined $session_id) { $session_id = 0; }
2899         if (not defined $sources_file) { 
2900                 &main::daemon_log("$session_id INFO: no sources_file given for creating packages list so trigger creation of it", 5); 
2901                 $sources_file = &create_sources_list($session_id);
2902         }
2904         if (not defined $sources_file) {
2905                 &main::daemon_log("$session_id ERROR: no sources_file given under '$sources_file', skip create_packages_list_db", 1); 
2906                 unlink($packages_list_under_construction);
2907                 return;
2908         }
2910         my $line;
2912         open(CONFIG, "<$sources_file") or do {
2913                 daemon_log( "$session_id ERROR: create_packages_list_db: Failed to open '$sources_file'", 1);
2914                 unlink($packages_list_under_construction);
2915                 return;
2916         };
2918         # Read lines
2919         while ($line = <CONFIG>){
2920                 # Unify
2921                 chop($line);
2922                 $line =~ s/^\s+//;
2923                 $line =~ s/^\s+/ /;
2925                 # Strip comments
2926                 $line =~ s/#.*$//g;
2928                 # Skip empty lines
2929                 if ($line =~ /^\s*$/){
2930                         next;
2931                 }
2933                 # Interpret deb line
2934                 if ($line =~ /^deb [^\s]+\s[^\s]+\s[^\s]+/){
2935                         my( $baseurl, $dist, $sections ) = ($line =~ /^deb\s([^\s]+)\s+([^\s]+)\s+(.*)$/);
2936                         my $section;
2937                         foreach $section (split(' ', $sections)){
2938                                 &parse_package_info( $baseurl, $dist, $section, $session_id );
2939                         }
2940                 }
2941         }
2943         close (CONFIG);
2945         if(keys(%repo_dirs)) {
2946                 find(\&cleanup_and_extract, keys( %repo_dirs ));
2947                 &main::strip_packages_list_statements();
2948                 $packages_list_db->exec_statementlist(\@packages_list_statements);
2949         }
2950         unlink($packages_list_under_construction);
2951         daemon_log("$session_id INFO: create_packages_list_db: finished", 5); 
2952         return;
2955 # This function should do some intensive task to minimize the db-traffic
2956 sub strip_packages_list_statements {
2957         my @existing_entries= @{$packages_list_db->exec_statement("SELECT * FROM $main::packages_list_tn")};
2958         my @new_statement_list=();
2959         my $hash;
2960         my $insert_hash;
2961         my $update_hash;
2962         my $delete_hash;
2963         my $known_packages_hash;
2964         my $local_timestamp=get_time();
2966         foreach my $existing_entry (@existing_entries) {
2967                 $hash->{@{$existing_entry}[0]}->{@{$existing_entry}[1]}->{@{$existing_entry}[2]}= $existing_entry;
2968         }
2970         foreach my $statement (@packages_list_statements) {
2971                 if($statement =~ /^INSERT/i) {
2972                         # Assign the values from the insert statement
2973                         my ($distribution,$package,$version,$section,$description,$template,$timestamp) = ($1,$2,$3,$4,$5,$6,$7) if $statement =~ 
2974                         /^INSERT\s+?INTO\s+?$main::packages_list_tn\s+?VALUES\s*?\('(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)'\s*?\)$/si;
2975                         if(exists($hash->{$distribution}->{$package}->{$version})) {
2976                                 # If section or description has changed, update the DB
2977                                 if( 
2978                                         (! (@{$hash->{$distribution}->{$package}->{$version}}[3] eq $section)) or 
2979                                         (! (@{$hash->{$distribution}->{$package}->{$version}}[4] eq $description))
2980                                 ) {
2981                                         @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,undef);
2982                                 } else {
2983                                         # package is already present in database. cache this knowledge for later use
2984                                         @{$known_packages_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
2985                                 }
2986                         } else {
2987                                 # Insert a non-existing entry to db
2988                                 @{$insert_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
2989                         }
2990                 } elsif ($statement =~ /^UPDATE/i) {
2991                         my ($template,$package,$version) = ($1,$2,$3) if $statement =~
2992                         /^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;
2993                         foreach my $distribution (keys %{$hash}) {
2994                                 if(exists($insert_hash->{$distribution}->{$package}->{$version})) {
2995                                         # update the insertion hash to execute only one query per package (insert instead insert+update)
2996                                         @{$insert_hash->{$distribution}->{$package}->{$version}}[5]= $template;
2997                                 } elsif(exists($hash->{$distribution}->{$package}->{$version})) {
2998                                         if( ! (@{$hash->{$distribution}->{$package}->{$version}}[5] eq $template)) {
2999                                                 my $section;
3000                                                 my $description;
3001                                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) and
3002                                                         length(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) > 0 ) {
3003                                                         $section= @{$update_hash->{$distribution}->{$package}->{$version}}[3];
3004                                                 }
3005                                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
3006                                                         $description= @{$update_hash->{$distribution}->{$package}->{$version}}[4];
3007                                                 }
3008                                                 @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
3009                                         }
3010                                 }
3011                         }
3012                 }
3013         }
3015         # Check for orphaned entries
3016         foreach my $existing_entry (@existing_entries) {
3017                 my $distribution= @{$existing_entry}[0];
3018                 my $package= @{$existing_entry}[1];
3019                 my $version= @{$existing_entry}[2];
3020                 my $section= @{$existing_entry}[3];
3022                 if(
3023                         exists($insert_hash->{$distribution}->{$package}->{$version}) ||
3024                         exists($update_hash->{$distribution}->{$package}->{$version}) ||
3025                         exists($known_packages_hash->{$distribution}->{$package}->{$version})
3026                 ) {
3027                         next;
3028                 } else {
3029                         # Insert entry to delete hash
3030                         @{$delete_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section);
3031                 }
3032         }
3034         # unroll the insert hash
3035         foreach my $distribution (keys %{$insert_hash}) {
3036                 foreach my $package (keys %{$insert_hash->{$distribution}}) {
3037                         foreach my $version (keys %{$insert_hash->{$distribution}->{$package}}) {
3038                                 push @new_statement_list, "INSERT INTO $main::packages_list_tn VALUES ('$distribution','$package','$version',"
3039                                 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[3]',"
3040                                 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[4]',"
3041                                 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[5]',"
3042                                 ."'$local_timestamp')";
3043                         }
3044                 }
3045         }
3047         # unroll the update hash
3048         foreach my $distribution (keys %{$update_hash}) {
3049                 foreach my $package (keys %{$update_hash->{$distribution}}) {
3050                         foreach my $version (keys %{$update_hash->{$distribution}->{$package}}) {
3051                                 my $set = "";
3052                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3])) {
3053                                         $set .= "section = '@{$update_hash->{$distribution}->{$package}->{$version}}[3]', ";
3054                                 }
3055                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
3056                                         $set .= "description = '@{$update_hash->{$distribution}->{$package}->{$version}}[4]', ";
3057                                 }
3058                                 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[5])) {
3059                                         $set .= "template = '@{$update_hash->{$distribution}->{$package}->{$version}}[5]', ";
3060                                 }
3061                                 if(defined($set) and length($set) > 0) {
3062                                         $set .= "timestamp = '$local_timestamp'";
3063                                 } else {
3064                                         next;
3065                                 }
3066                                 push @new_statement_list, 
3067                                 "UPDATE $main::packages_list_tn SET $set WHERE"
3068                                 ." distribution = '$distribution'"
3069                                 ." AND package = '$package'"
3070                                 ." AND version = '$version'";
3071                         }
3072                 }
3073         }
3074         
3075         # unroll the delete hash
3076         foreach my $distribution (keys %{$delete_hash}) {
3077                 foreach my $package (keys %{$delete_hash->{$distribution}}) {
3078                         foreach my $version (keys %{$delete_hash->{$distribution}->{$package}}) {
3079                                 my $section = @{$delete_hash->{$distribution}->{$package}->{$version}}[3];
3080                                 push @new_statement_list, "DELETE FROM $main::packages_list_tn WHERE distribution='$distribution' AND package='$package' AND version='$version' AND section='$section'";
3081                         }
3082                 }
3083         }
3085         unshift(@new_statement_list, "VACUUM");
3087         @packages_list_statements = @new_statement_list;
3091 sub parse_package_info {
3092     my ($baseurl, $dist, $section, $session_id)= @_;
3093     my ($package);
3094     if (not defined $session_id) { $session_id = 0; }
3095     my ($path) = ($baseurl =~ m%://[^/]*(.*)$%);
3096     $repo_dirs{ "${repo_path}/pool" } = 1;
3098     foreach $package ("Packages.gz"){
3099         daemon_log("$session_id DEBUG: create_packages_list: fetch $baseurl, $dist, $section", 7);
3100         get_package( "$baseurl/dists/$dist/$section/binary-$arch/$package", "$outdir/$dist/$section", $session_id );
3101         parse_package( "$outdir/$dist/$section", $dist, $path, $session_id );
3102     }
3103     
3107 sub get_package {
3108     my ($url, $dest, $session_id)= @_;
3109     if (not defined $session_id) { $session_id = 0; }
3111     my $tpath = dirname($dest);
3112     -d "$tpath" || mkpath "$tpath";
3114     # This is ugly, but I've no time to take a look at "how it works in perl"
3115     if(0 == system("wget '$url' -O '$dest' 2>/dev/null") ) {
3116         system("gunzip -cd '$dest' > '$dest.in'");
3117         daemon_log("$session_id DEBUG: run command: gunzip -cd '$dest' > '$dest.in'", 7);
3118         unlink($dest);
3119         daemon_log("$session_id DEBUG: delete file '$dest'", 7); 
3120     } else {
3121         daemon_log("$session_id ERROR: create_packages_list_db: get_packages: fetching '$url' into '$dest' failed!", 1);
3122     }
3123     return 0;
3127 sub parse_package {
3128     my ($path, $dist, $srv_path, $session_id)= @_;
3129     if (not defined $session_id) { $session_id = 0;}
3130     my ($package, $version, $section, $description);
3131     my $PACKAGES;
3132     my $timestamp = &get_time();
3134     if(not stat("$path.in")) {
3135         daemon_log("$session_id ERROR: create_packages_list: parse_package: file '$path.in' is not readable",1);
3136         return;
3137     }
3139     open($PACKAGES, "<$path.in");
3140     if(not defined($PACKAGES)) {
3141         daemon_log("$session_id ERROR: create_packages_list_db: parse_package: cannot open '$path.in'",1); 
3142         return;
3143     }
3145     # Read lines
3146     while (<$PACKAGES>){
3147         my $line = $_;
3148         # Unify
3149         chop($line);
3151         # Use empty lines as a trigger
3152         if ($line =~ /^\s*$/){
3153             my $sql = "INSERT INTO packages_list VALUES ('$dist', '$package', '$version', '$section', '$description', '', '$timestamp')";
3154             push(@packages_list_statements, $sql);
3155             $package = "none";
3156             $version = "none";
3157             $section = "none";
3158             $description = "none"; 
3159             next;
3160         }
3162         # Trigger for package name
3163         if ($line =~ /^Package:\s/){
3164             ($package)= ($line =~ /^Package: (.*)$/);
3165             next;
3166         }
3168         # Trigger for version
3169         if ($line =~ /^Version:\s/){
3170             ($version)= ($line =~ /^Version: (.*)$/);
3171             next;
3172         }
3174         # Trigger for description
3175         if ($line =~ /^Description:\s/){
3176             ($description)= &encode_base64(($line =~ /^Description: (.*)$/));
3177             next;
3178         }
3180         # Trigger for section
3181         if ($line =~ /^Section:\s/){
3182             ($section)= ($line =~ /^Section: (.*)$/);
3183             next;
3184         }
3186         # Trigger for filename
3187         if ($line =~ /^Filename:\s/){
3188             my ($filename) = ($line =~ /^Filename: (.*)$/);
3189             store_fileinfo( $package, $filename, $dist, $srv_path, $version, $repo_path );
3190             next;
3191         }
3192     }
3194     close( $PACKAGES );
3195     unlink( "$path.in" );
3199 sub store_fileinfo {
3200     my( $package, $file, $dist, $path, $vers, $srvdir) = @_;
3202     my %fileinfo = (
3203         'package' => $package,
3204         'dist' => $dist,
3205         'version' => $vers,
3206     );
3208     $repo_files{ "${srvdir}/$file" } = \%fileinfo;
3212 sub cleanup_and_extract {
3213         my $fileinfo = $repo_files{ $File::Find::name };
3215         if( defined $fileinfo ) {
3216                 my $dir = "$outdir/$fileinfo->{ 'dist' }/debconf.d";
3217                 my $sql;
3218                 my $package = $fileinfo->{ 'package' };
3219                 my $newver = $fileinfo->{ 'version' };
3221                 mkpath($dir);
3222                 system( "dpkg -e '$File::Find::name' '$dir/DEBIAN'" );
3224                 if( -f "$dir/DEBIAN/templates" ) {
3226                         daemon_log("0 DEBUG: Found debconf templates in '$package' - $newver", 7);
3228                         my $tmpl= ""; {
3229                                 local $/=undef;
3230                                 open FILE, "$dir/DEBIAN/templates";
3231                                 $tmpl = &encode_base64(<FILE>);
3232                                 close FILE;
3233                         }
3234                         rmtree("$dir/DEBIAN/templates");
3236                         $sql= "update $main::packages_list_tn set template = '$tmpl' where package = '$package' and version = '$newver';";
3237                         push @packages_list_statements, $sql;
3238                 }
3239         }
3241         return;
3245 sub register_at_foreign_servers {   
3246     my ($kernel) = $_[KERNEL];
3248         # Update status and update-time of all si-server with expired update_time and 
3249         # block them for race conditional registration processes of other si-servers.
3250         my $act_time = &get_time();
3251         my $block_statement = "UPDATE $known_server_tn SET status='new_server',update_time='19700101000000' WHERE (CAST(update_time AS UNSIGNED))<$act_time ";
3252         my $block_res = $known_server_db->exec_statement($block_statement);
3254         # Fetch all si-server from db where update_time is younger than act_time
3255         my $fetch_statement = "SELECT * FROM $known_server_tn WHERE update_time='19700101000000'"; 
3256         my $fetch_res = $known_server_db->exec_statement($fetch_statement);
3258     # Detect already connected clients. Will be added to registration msg later. 
3259     my $client_sql = "SELECT * FROM $known_clients_tn"; 
3260     my $client_res = $known_clients_db->exec_statement($client_sql);
3262         # Send registration messag to all fetched si-server
3263     foreach my $hit (@$fetch_res) {
3264         my $hostname = @$hit[0];
3265         my $hostkey = &create_passwd;
3267         # Add already connected clients to registration message 
3268         my $myhash = &create_xml_hash('new_server', $server_address, $hostname);
3269         &add_content2xml_hash($myhash, 'key', $hostkey);
3270         map(&add_content2xml_hash($myhash, 'client', @{$_}[0].",".@{$_}[4]), @$client_res);
3272         # Add locally loaded gosa-si modules to registration message
3273         my $loaded_modules = {};
3274         while (my ($package, $pck_info) = each %$known_modules) {
3275                         next if ((!defined(@$pck_info[2])) || (!(ref (@$pck_info[2]) eq 'HASH')));
3276                         foreach my $act_module (keys(%{@$pck_info[2]})) {
3277                                 $loaded_modules->{$act_module} = ""; 
3278                         }
3279                 }
3280         map(&add_content2xml_hash($myhash, "loaded_modules", $_), keys(%$loaded_modules));
3282         # Add macaddress to registration message
3283         my ($host_ip, $host_port) = split(/:/, $hostname);
3284         my $local_ip = &get_local_ip_for_remote_ip($host_ip);
3285         my $network_interface= &get_interface_for_ip($local_ip);
3286         my $host_mac = &get_mac_for_interface($network_interface);
3287         &add_content2xml_hash($myhash, 'macaddress', $host_mac);
3288         
3289         # Build registration message and send it
3290         my $foreign_server_msg = &create_xml_string($myhash);
3291         my $error = &send_msg_to_target($foreign_server_msg, $hostname, $ServerPackages_key, "new_server", 0); 
3292     }
3295         # After n sec perform a check of all server registration processes
3296     $kernel->delay_set("control_server_registration", 2); 
3298         return;
3302 sub control_server_registration {
3303         my ($kernel) = $_[KERNEL];
3304         
3305         # Check if all registration processes succeed or not
3306         my $select_statement = "SELECT * FROM $known_server_tn WHERE status='new_server'"; 
3307         my $select_res = $known_server_db->exec_statement($select_statement);
3309         # If at least one registration process failed, maybe in case of a race condition
3310         # with a foreign registration process
3311         if (@$select_res > 0) 
3312         {
3313                 # Release block statement 'new_server' to make the server accessible
3314                 # for foreign registration processes
3315                 my $update_statement = "UPDATE $known_server_tn SET status='waiting' WHERE status='new_server'";        
3316                 my $update_res = $known_server_db->exec_statement($update_statement);
3318                 # Set a random delay to avoid the registration race condition
3319                 my $new_foreign_servers_register_delay = int(rand(4))+1;
3320                 $kernel->delay_set("register_at_foreign_servers", $new_foreign_servers_register_delay);
3321         }
3322         # If all registration processes succeed
3323         else
3324         {
3325                 $kernel->delay_set("register_at_foreign_servers", $foreign_servers_register_delay);
3326         }
3328         return;
3332 #==== MAIN = main ==============================================================
3333 #  parse commandline options
3334 Getopt::Long::Configure( "bundling" );
3335 GetOptions("h|help" => \&usage,
3336         "c|config=s" => \$cfg_file,
3337         "f|foreground" => \$foreground,
3338         "v|verbose+" => \$verbose,
3339         "no-arp+" => \$no_arp,
3340                 "d=s" => \$debug_parts,
3341            ) or (&usage("", 1)&&(exit(-1))); 
3343 #  read and set config parameters
3344 &check_cmdline_param ;
3345 &read_configfile($cfg_file, %cfg_defaults);
3346 &check_pid;
3348 $SIG{CHLD} = 'IGNORE';
3350 # forward error messages to logfile
3351 if( ! $foreground ) {
3352   open( STDIN,  '+>/dev/null' );
3353   open( STDOUT, '+>&STDIN'    );
3354   open( STDERR, '+>&STDIN'    );
3357 # Just fork, if we are not in foreground mode
3358 if( ! $foreground ) { 
3359     chdir '/'                 or die "Can't chdir to /: $!";
3360     $pid = fork;
3361     setsid                    or die "Can't start a new session: $!";
3362     umask 0;
3363 } else { 
3364     $pid = $$; 
3367 # Do something useful - put our PID into the pid_file
3368 if( 0 != $pid ) {
3369     open( LOCK_FILE, ">$pid_file" );
3370     print LOCK_FILE "$pid\n";
3371     close( LOCK_FILE );
3372     if( !$foreground ) { 
3373         exit( 0 ) 
3374     };
3377 # parse head url and revision from svn
3378 my $server_status_hash = { 'developmental'=>'revision', 'stable'=>'release'};
3379 $server_version =~ /^\$HeadURL: (\S+) \$:\$Rev: (\d+) \$$/;
3380 $server_headURL = defined $1 ? $1 : 'unknown' ;
3381 $server_revision = defined $2 ? $2 : 'unknown' ;
3382 if ($server_headURL =~ /\/tag\// || 
3383         $server_headURL =~ /\/branches\// ) {
3384     $server_status = "stable"; 
3385 } else {
3386     $server_status = "developmental" ;
3388 # Prepare log file and set permissions
3389 $root_uid = getpwnam('root');
3390 $adm_gid = getgrnam('adm');
3391 open(FH, ">>$log_file");
3392 close FH;
3393 chmod(0440, $log_file);
3394 chown($root_uid, $adm_gid, $log_file);
3395 chown($root_uid, $adm_gid, "/var/lib/gosa-si");
3397 daemon_log(" ", 1);
3398 daemon_log("$0 started!", 1);
3399 daemon_log("status: $server_status", 1);
3400 daemon_log($server_status_hash->{$server_status}.": $server_revision", 1); 
3402 # Buildup data bases
3404     no strict "refs";
3406     if ($db_module eq "DBmysql") {
3407         # connect to incoming_db
3408         $incoming_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3410         # connect to gosa-si job queue
3411         $job_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3413         # connect to known_clients_db
3414         $known_clients_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3416         # connect to foreign_clients_db
3417         $foreign_clients_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3419         # connect to known_server_db
3420         $known_server_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3422         # connect to login_usr_db
3423         $login_users_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3425         # connect to fai_server_db 
3426         $fai_server_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3428         # connect to fai_release_db
3429         $fai_release_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3431         # connect to packages_list_db
3432         $packages_list_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3434         # connect to messaging_db
3435         $messaging_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3437     } elsif ($db_module eq "DBsqlite") {
3438         # connect to incoming_db
3439         unlink($incoming_file_name);
3440         $incoming_db = GOSA::DBsqlite->new($incoming_file_name);
3441         chmod(0640, $incoming_file_name);
3442         chown($root_uid, $adm_gid, $incoming_file_name);
3443         
3444         # connect to gosa-si job queue
3445         $job_db = GOSA::DBsqlite->new($job_queue_file_name);
3446         chmod(0640, $job_queue_file_name);
3447         chown($root_uid, $adm_gid, $job_queue_file_name);
3448         
3449         # connect to known_clients_db
3450         #unlink($known_clients_file_name);
3451         $known_clients_db = GOSA::DBsqlite->new($known_clients_file_name);
3452         chmod(0640, $known_clients_file_name);
3453         chown($root_uid, $adm_gid, $known_clients_file_name);
3454         
3455         # connect to foreign_clients_db
3456         #unlink($foreign_clients_file_name);
3457         $foreign_clients_db = GOSA::DBsqlite->new($foreign_clients_file_name);
3458         chmod(0640, $foreign_clients_file_name);
3459         chown($root_uid, $adm_gid, $foreign_clients_file_name);
3460         
3461         # connect to known_server_db
3462         #unlink($known_server_file_name);
3463         $known_server_db = GOSA::DBsqlite->new($known_server_file_name);
3464         chmod(0640, $known_server_file_name);
3465         chown($root_uid, $adm_gid, $known_server_file_name);
3466         
3467         # connect to login_usr_db
3468         #unlink($login_users_file_name);
3469         $login_users_db = GOSA::DBsqlite->new($login_users_file_name);
3470         chmod(0640, $login_users_file_name);
3471         chown($root_uid, $adm_gid, $login_users_file_name);
3472         
3473         # connect to fai_server_db
3474         unlink($fai_server_file_name);
3475         $fai_server_db = GOSA::DBsqlite->new($fai_server_file_name);
3476         chmod(0640, $fai_server_file_name);
3477         chown($root_uid, $adm_gid, $fai_server_file_name);
3478         
3479         # connect to fai_release_db
3480         unlink($fai_release_file_name);
3481         $fai_release_db = GOSA::DBsqlite->new($fai_release_file_name);
3482         chmod(0640, $fai_release_file_name);
3483         chown($root_uid, $adm_gid, $fai_release_file_name);
3484         
3485         # connect to packages_list_db
3486         unlink($packages_list_under_construction);
3487         $packages_list_db = GOSA::DBsqlite->new($packages_list_file_name);
3488         chmod(0640, $packages_list_file_name);
3489         chown($root_uid, $adm_gid, $packages_list_file_name);
3490         
3491         # connect to messaging_db
3492         #unlink($messaging_file_name);
3493         $messaging_db = GOSA::DBsqlite->new($messaging_file_name);
3494         chmod(0640, $messaging_file_name);
3495         chown($root_uid, $adm_gid, $messaging_file_name);
3496     }
3500 # Creating tables
3501 $messaging_db->create_table($messaging_tn, \@messaging_col_names);
3502 $packages_list_db->create_table($packages_list_tn, \@packages_list_col_names);
3503 $fai_release_db->create_table($fai_release_tn, \@fai_release_col_names);
3504 $fai_server_db->create_table($fai_server_tn, \@fai_server_col_names);
3505 $login_users_db->create_table($login_users_tn, \@login_users_col_names);
3506 $known_server_db->create_table($known_server_tn, \@known_server_col_names);
3507 $foreign_clients_db->create_table($foreign_clients_tn, \@foreign_clients_col_names);
3508 $known_clients_db->create_table($known_clients_tn, \@known_clients_col_names);
3509 $incoming_db->create_table($incoming_tn, \@incoming_col_names);
3510 $job_db->create_table($job_queue_tn, \@job_queue_col_names);
3512 # create xml object used for en/decrypting
3513 $xml = new XML::Simple();
3516 # foreign servers 
3517 my @foreign_server_list;
3519 # add foreign server from cfg file
3520 if ($foreign_server_string ne "") {
3521     my @cfg_foreign_server_list = split(",", $foreign_server_string);
3522     foreach my $foreign_server (@cfg_foreign_server_list) {
3523         push(@foreign_server_list, $foreign_server);
3524     }
3526     daemon_log("0 INFO: found foreign server in config file: ".join(", ", @foreign_server_list), 5);
3529 # Perform a DNS lookup for server registration if flag is true
3530 if ($dns_lookup eq "true") {
3531     # Add foreign server from dns
3532     my @tmp_servers;
3533     if (not $server_domain) {
3534         # Try our DNS Searchlist
3535         for my $domain(get_dns_domains()) {
3536             chomp($domain);
3537             my ($tmp_domains, $error_string) = &get_server_addresses($domain);
3538             if(@$tmp_domains) {
3539                 for my $tmp_server(@$tmp_domains) {
3540                     push @tmp_servers, $tmp_server;
3541                 }
3542             }
3543         }
3544         if(@tmp_servers && length(@tmp_servers)==0) {
3545             daemon_log("0 WARNING: no foreign gosa-si-server found in DNS for domain '$server_domain'", 3);
3546         }
3547     } else {
3548         @tmp_servers = &get_server_addresses($server_domain);
3549         if( 0 == @tmp_servers ) {
3550             daemon_log("0 WARNING: no foreign gosa-si-server found in DNS for domain '$server_domain'", 3);
3551         }
3552     }
3554     daemon_log("0 INFO: found foreign server via DNS ".join(", ", @tmp_servers), 5);    
3556     foreach my $server (@tmp_servers) { 
3557         unshift(@foreign_server_list, $server); 
3558     }
3559 } else {
3560     daemon_log("0 INFO: DNS lookup for server registration is disabled", 5);
3564 # eliminate duplicate entries
3565 @foreign_server_list = &del_doubles(@foreign_server_list);
3566 my $all_foreign_server = join(", ", @foreign_server_list);
3567 daemon_log("0 INFO: found foreign server in config file and DNS: '$all_foreign_server'", 5);
3569 # add all found foreign servers to known_server
3570 my $cur_timestamp = &get_time();
3571 foreach my $foreign_server (@foreign_server_list) {
3573         # do not add myself to known_server_db
3574         if (&is_local($foreign_server)) { next; }
3575         ######################################
3577     my $res = $known_server_db->add_dbentry( {table=>$known_server_tn, 
3578             primkey=>['hostname'],
3579             hostname=>$foreign_server,
3580             macaddress=>"",
3581             status=>'not_yet_registered',
3582             hostkey=>"none",
3583             loaded_modules => "none", 
3584             timestamp=>$cur_timestamp,
3585                         update_time=>'19700101000000',
3586             } );
3590 # Import all modules
3591 &import_modules;
3593 # Check wether all modules are gosa-si valid passwd check
3594 &password_check;
3596 # Create functions hash
3597 #print STDERR Dumper $known_modules;
3598 while (my ($module, @mod_info) = each %$known_modules) 
3600 #print STDERR Dumper $module;
3601         while (my ($plugin, $functions) = each %{$mod_info[0][2]})
3602         {
3603 #print STDERR Dumper $functions;
3604                 while (my ($function, $nothing) = each %$functions )
3605                 {
3606                         $known_functions->{$function} = $nothing;
3607                 }
3608         }
3611 # Prepare for using Opsi 
3612 if ($opsi_enabled eq "true") {
3613     use JSON::RPC::Client;
3614     use XML::Quote qw(:all);
3615     $opsi_url= "https://".$opsi_admin.":".$opsi_password."@".$opsi_server.":4447/rpc";
3616     $opsi_client = new JSON::RPC::Client;
3620 POE::Component::Server::TCP->new(
3621         Alias => "TCP_SERVER",
3622         Port => $server_port,
3623         ClientInput => sub {
3624                 my ($kernel, $input, $heap, $session) = @_[KERNEL, ARG0, HEAP, SESSION];
3625         my $session_id = $session->ID;
3626                 if ($input =~ /;([\d\.]+:[\d]+)$/) 
3627                 {
3628                         &daemon_log("$session_id DEBUG: incoming message from '$1'", 11);
3629                 }
3630                 else
3631                 {
3632                         my $remote_ip = $heap->{'remote_ip'};
3633                         &daemon_log("$session_id DEBUG: incoming message from '$remote_ip'", 11);
3634                 }
3635                 push(@msgs_to_decrypt, $input);
3636                 $kernel->yield("msg_to_decrypt");
3637         },
3638         InlineStates => {
3639                 msg_to_decrypt => \&msg_to_decrypt,
3640                 next_task => \&next_task,
3641                 task_result => \&handle_task_result,
3642                 task_done   => \&handle_task_done,
3643                 task_debug  => \&handle_task_debug,
3644                 child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!"  },
3645         }
3646 );
3648 daemon_log("0 INFO: start socket for incoming xml messages at port '$server_port' ", 1);
3650 # create session for repeatedly checking the job queue for jobs
3651 POE::Session->create(
3652         inline_states => {
3653                 _start => \&session_start,
3654         register_at_foreign_servers => \&register_at_foreign_servers,
3655                 control_server_registration => \&control_server_registration,
3656         sig_handler => \&sig_handler,
3657         next_task => \&next_task,
3658         task_result => \&handle_task_result,
3659         task_done   => \&handle_task_done,
3660         task_debug  => \&handle_task_debug,
3661         watch_for_next_tasks => \&watch_for_next_tasks,
3662         watch_for_new_messages => \&watch_for_new_messages,
3663         watch_for_delivery_messages => \&watch_for_delivery_messages,
3664         watch_for_done_messages => \&watch_for_done_messages,
3665                 watch_for_new_jobs => \&watch_for_new_jobs,
3666         watch_for_modified_jobs => \&watch_for_modified_jobs,
3667         watch_for_done_jobs => \&watch_for_done_jobs,
3668         watch_for_opsi_jobs => \&watch_for_opsi_jobs,
3669         watch_for_old_known_clients => \&watch_for_old_known_clients,
3670         create_packages_list_db => \&run_create_packages_list_db,
3671         create_fai_server_db => \&run_create_fai_server_db,
3672         create_fai_release_db => \&run_create_fai_release_db,
3673                 recreate_packages_db => \&run_recreate_packages_db,
3674         session_run_result => \&session_run_result,
3675         session_run_debug => \&session_run_debug,
3676         session_run_done => \&session_run_done,
3677         child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!"  },
3678         }
3679 );
3682 POE::Kernel->run();
3683 exit;