Code

018d67f4f61c61c45f6244680b1c66e58176cfbf
[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 use strict;
24 use warnings;
25 use Getopt::Long;
26 use Config::IniFiles;
27 use POSIX;
29 use Fcntl;
30 use IO::Socket::INET;
31 use IO::Handle;
32 use IO::Select;
33 use Symbol qw(qualify_to_ref);
34 use Crypt::Rijndael;
35 use MIME::Base64;
36 use Digest::MD5  qw(md5 md5_hex md5_base64);
37 use XML::Simple;
38 use Data::Dumper;
39 use Sys::Syslog qw( :DEFAULT setlogsock);
40 use Cwd;
41 use File::Spec;
42 use File::Basename;
43 use File::Find;
44 use File::Copy;
45 use File::Path;
46 use GOSA::DBsqlite;
47 use GOSA::GosaSupportDaemon;
48 use POE qw(Component::Server::TCP Wheel::Run Filter::Reference);
49 use Net::LDAP;
50 use Net::LDAP::Util qw(:escape);
52 my $modules_path = "/usr/lib/gosa-si/modules";
53 use lib "/usr/lib/gosa-si/modules";
55 my (%cfg_defaults, $foreground, $verbose, $ping_timeout);
56 my ($bus_activ, $bus, $msg_to_bus, $bus_cipher);
57 my ($server);
58 my ($gosa_server, $job_queue_timeout, $job_queue_loop_delay);
59 my ($known_modules);
60 my ($pid_file, $procid, $pid, $log_file);
61 my ($arp_activ, $arp_fifo);
62 my ($xml);
63 my $sources_list;
64 my $max_clients;
65 my %repo_files=();
66 my $repo_path;
67 my %repo_dirs=();
68 # variables declared in config file are always set to 'our'
69 our (%cfg_defaults, $log_file, $pid_file, 
70     $server_ip, $server_port, $SIPackages_key, 
71     $arp_activ, $gosa_unit_tag,
72     $GosaPackages_key, $gosa_ip, $gosa_port, $gosa_timeout,
73 );
75 # additional variable which should be globaly accessable
76 our $server_address;
77 our $server_mac_address;
78 our $bus_address;
79 our $gosa_address;
80 our $no_bus;
81 our $no_arp;
82 our $verbose;
83 our $forground;
84 our $cfg_file;
85 our ($ldap_handle, $ldap_uri, $ldap_base, $ldap_admin_dn, $ldap_admin_password, $ldap_server_dn);
88 # specifies the verbosity of the daemon_log
89 $verbose = 0 ;
91 # if foreground is not null, script will be not forked to background
92 $foreground = 0 ;
94 # specifies the timeout seconds while checking the online status of a registrating client
95 $ping_timeout = 5;
97 $no_bus = 0;
98 $bus_activ = "true";
100 $no_arp = 0;
102 our $prg= basename($0);
104 # holds all gosa jobs
105 our $job_db;
106 our $job_queue_tn = 'jobs';
107 my $job_queue_file_name;
108 my @job_queue_col_names = ("id INTEGER", 
109                 "timestamp", 
110                 "status DEFAULT 'none'", 
111                 "result DEFAULT 'none'", 
112                 "progress DEFAULT 'none'", 
113                 "headertag DEFAULT 'none'", 
114                 "targettag DEFAULT 'none'", 
115                 "xmlmessage DEFAULT 'none'", 
116                 "macaddress DEFAULT 'none'",
117                 );
119 # holds all other gosa-sd as well as the gosa-sd-bus
120 our $known_server_db;
121 our $known_server_tn = "known_server";
122 my $known_server_file_name;
123 my @known_server_col_names = ('hostname', 'status', 'hostkey', 'timestamp');
125 # holds all registrated clients
126 our $known_clients_db;
127 our $known_clients_tn = "known_clients";
128 my $known_clients_file_name;
129 my @known_clients_col_names = ('hostname', 'status', 'hostkey', 'timestamp', 'macaddress', 'events');
131 # holds all logged in user at each client 
132 our $login_users_db;
133 our $login_users_tn = "login_users";
134 my $login_users_file_name;
135 my @login_users_col_names = ('client', 'user', 'timestamp');
137 # holds all fai server, the debian release and tag
138 our $fai_server_db;
139 our $fai_server_tn = "fai_server"; 
140 my $fai_server_file_name;
141 our @fai_server_col_names = ('timestamp', 'server', 'release', 'sections', 'tag'); 
142 our $fai_release_tn = "fai_release"; 
143 our @fai_release_col_names = ('timestamp', 'release', 'class', 'type', 'state'); 
145 # holds all packages available from different repositories
146 our $packages_list_db;
147 our $packages_list_tn = "packages_list";
148 my $packages_list_file_name;
149 our @packages_list_col_names = ('distribution', 'package', 'version', 'section', 'description', 'template', 'timestamp');
150 my $outdir = "/tmp/packages_list_db";
151 my $arch = "i386"; 
153 # holds all messages which should be delivered to a user
154 our $messaging_db;
155 our $messaging_tn = "messaging"; 
156 our @messaging_col_names = ('subject', 'from', 'to', 'flag', 'direction', 'delivery_time', 'message', 'timestamp', 'id INTEGER', );
157 my $messaging_file_name;
159 # path to directory to store client install log files
160 our $client_fai_log_dir = "/var/log/fai"; 
162 # queue which stores taskes until one of the $max_children children are ready to process the task
163 my @tasks = qw();
164 my $max_children = 2;
167 %cfg_defaults = (
168 "general" => {
169     "log-file" => [\$log_file, "/var/run/".$prg.".log"],
170     "pid-file" => [\$pid_file, "/var/run/".$prg.".pid"],
171     },
172 "bus" => {
173     "activ" => [\$bus_activ, "true"],
174     },
175 "server" => {
176     "port" => [\$server_port, "20081"],
177     "known-clients" => [\$known_clients_file_name, '/var/lib/gosa-si/clients.db' ],
178     "known-servers" => [\$known_server_file_name, '/var/lib/gosa-si/servers.db'],
179     "login-users" => [\$login_users_file_name, '/var/lib/gosa-si/users.db'],
180     "fai-server" => [\$fai_server_file_name, '/var/lib/gosa-si/fai.db'],
181     "packages-list" => [\$packages_list_file_name, '/var/lib/gosa-si/packages.db'],
182     "messaging" => [\$messaging_file_name, '/var/lib/gosa-si/messaging.db'],
183     "source-list" => [\$sources_list, '/etc/apt/sources.list'],
184     "repo-path" => [\$repo_path, '/srv/www/repository'],
185     "ldap-uri" => [\$ldap_uri, ""],
186     "ldap-base" => [\$ldap_base, ""],
187     "ldap-admin-dn" => [\$ldap_admin_dn, ""],
188     "ldap-admin-password" => [\$ldap_admin_password, ""],
189     "gosa-unit-tag" => [\$gosa_unit_tag, ""],
190     "max-clients" => [\$max_clients, 10],
191     },
192 "GOsaPackages" => {
193     "ip" => [\$gosa_ip, "0.0.0.0"],
194     "port" => [\$gosa_port, "20082"],
195     "job-queue" => [\$job_queue_file_name, '/var/lib/gosa-si/jobs.db'],
196     "job-queue-loop-delay" => [\$job_queue_loop_delay, 3],
197     "key" => [\$GosaPackages_key, "none"],
198     },
199 "SIPackages" => {
200     "key" => [\$SIPackages_key, "none"],
201     },
202 );
205 #===  FUNCTION  ================================================================
206 #         NAME:  usage
207 #   PARAMETERS:  nothing
208 #      RETURNS:  nothing
209 #  DESCRIPTION:  print out usage text to STDERR
210 #===============================================================================
211 sub usage {
212     print STDERR << "EOF" ;
213 usage: $prg [-hvf] [-c config]
215            -h        : this (help) message
216            -c <file> : config file
217            -f        : foreground, process will not be forked to background
218            -v        : be verbose (multiple to increase verbosity)
219            -no-bus   : starts $prg without connection to bus
220            -no-arp   : starts $prg without connection to arp module
221  
222 EOF
223     print "\n" ;
227 #===  FUNCTION  ================================================================
228 #         NAME:  read_configfile
229 #   PARAMETERS:  cfg_file - string -
230 #      RETURNS:  nothing
231 #  DESCRIPTION:  read cfg_file and set variables
232 #===============================================================================
233 sub read_configfile {
234     my $cfg;
235     if( defined( $cfg_file) && ( length($cfg_file) > 0 )) {
236         if( -r $cfg_file ) {
237             $cfg = Config::IniFiles->new( -file => $cfg_file );
238         } else {
239             print STDERR "Couldn't read config file!\n";
240         }
241     } else {
242         $cfg = Config::IniFiles->new() ;
243     }
244     foreach my $section (keys %cfg_defaults) {
245         foreach my $param (keys %{$cfg_defaults{ $section }}) {
246             my $pinfo = $cfg_defaults{ $section }{ $param };
247             ${@$pinfo[ 0 ]} = $cfg->val( $section, $param, @$pinfo[ 1 ] );
248         }
249     }
253 #===  FUNCTION  ================================================================
254 #         NAME:  logging
255 #   PARAMETERS:  level - string - default 'info'
256 #                msg - string -
257 #                facility - string - default 'LOG_DAEMON'
258 #      RETURNS:  nothing
259 #  DESCRIPTION:  function for logging
260 #===============================================================================
261 sub daemon_log {
262     # log into log_file
263     my( $msg, $level ) = @_;
264     if(not defined $msg) { return }
265     if(not defined $level) { $level = 1 }
266     if(defined $log_file){
267         open(LOG_HANDLE, ">>$log_file");
268         if(not defined open( LOG_HANDLE, ">>$log_file" )) {
269             print STDERR "cannot open $log_file: $!";
270             return }
271             chomp($msg);
272             if($level <= $verbose){
273                 my ($seconds, $minutes, $hours, $monthday, $month,
274                         $year, $weekday, $yearday, $sommertime) = localtime(time);
275                 $hours = $hours < 10 ? $hours = "0".$hours : $hours;
276                 $minutes = $minutes < 10 ? $minutes = "0".$minutes : $minutes;
277                 $seconds = $seconds < 10 ? $seconds = "0".$seconds : $seconds;
278                 my @monthnames = ("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec");
279                 $month = $monthnames[$month];
280                 $monthday = $monthday < 10 ? $monthday = "0".$monthday : $monthday;
281                 $year+=1900;
282                 my $name = $prg;
284                 my $log_msg = "$month $monthday $hours:$minutes:$seconds $name $msg\n";
285                 print LOG_HANDLE $log_msg;
286                 if( $foreground ) { 
287                     print STDERR $log_msg;
288                 }
289             }
290         close( LOG_HANDLE );
291     }
295 #===  FUNCTION  ================================================================
296 #         NAME:  check_cmdline_param
297 #   PARAMETERS:  nothing
298 #      RETURNS:  nothing
299 #  DESCRIPTION:  validates commandline parameter
300 #===============================================================================
301 sub check_cmdline_param () {
302     my $err_config;
303     my $err_counter = 0;
304         if(not defined($cfg_file)) {
305                 $cfg_file = "/etc/gosa-si/server.conf";
306                 if(! -r $cfg_file) {
307                         $err_config = "please specify a config file";
308                         $err_counter += 1;
309                 }
310     }
311     if( $err_counter > 0 ) {
312         &usage( "", 1 );
313         if( defined( $err_config)) { print STDERR "$err_config\n"}
314         print STDERR "\n";
315         exit( -1 );
316     }
320 #===  FUNCTION  ================================================================
321 #         NAME:  check_pid
322 #   PARAMETERS:  nothing
323 #      RETURNS:  nothing
324 #  DESCRIPTION:  handels pid processing
325 #===============================================================================
326 sub check_pid {
327     $pid = -1;
328     # Check, if we are already running
329     if( open(LOCK_FILE, "<$pid_file") ) {
330         $pid = <LOCK_FILE>;
331         if( defined $pid ) {
332             chomp( $pid );
333             if( -f "/proc/$pid/stat" ) {
334                 my($stat) = `cat /proc/$pid/stat` =~ m/$pid \((.+)\).*/;
335                 if( $stat ) {
336                                         daemon_log("ERROR: Already running",1);
337                     close( LOCK_FILE );
338                     exit -1;
339                 }
340             }
341         }
342         close( LOCK_FILE );
343         unlink( $pid_file );
344     }
346     # create a syslog msg if it is not to possible to open PID file
347     if (not sysopen(LOCK_FILE, $pid_file, O_WRONLY|O_CREAT|O_EXCL, 0644)) {
348         my($msg) = "Couldn't obtain lockfile '$pid_file' ";
349         if (open(LOCK_FILE, '<', $pid_file)
350                 && ($pid = <LOCK_FILE>))
351         {
352             chomp($pid);
353             $msg .= "(PID $pid)\n";
354         } else {
355             $msg .= "(unable to read PID)\n";
356         }
357         if( ! ($foreground) ) {
358             openlog( $0, "cons,pid", "daemon" );
359             syslog( "warning", $msg );
360             closelog();
361         }
362         else {
363             print( STDERR " $msg " );
364         }
365         exit( -1 );
366     }
369 #===  FUNCTION  ================================================================
370 #         NAME:  import_modules
371 #   PARAMETERS:  module_path - string - abs. path to the directory the modules 
372 #                are stored
373 #      RETURNS:  nothing
374 #  DESCRIPTION:  each file in module_path which ends with '.pm' and activation 
375 #                state is on is imported by "require 'file';"
376 #===============================================================================
377 sub import_modules {
378     daemon_log(" ", 1);
380     if (not -e $modules_path) {
381         daemon_log("ERROR: cannot find directory or directory is not readable: $modules_path", 1);   
382     }
384     opendir (DIR, $modules_path) or die "ERROR while loading modules from directory $modules_path : $!\n";
385     while (defined (my $file = readdir (DIR))) {
386         if (not $file =~ /(\S*?).pm$/) {
387             next;
388         }
389                 my $mod_name = $1;
391         if( $file =~ /ArpHandler.pm/ ) {
392             if( $no_arp > 0 ) {
393                 next;
394             }
395         }
396         
397         eval { require $file; };
398         if ($@) {
399             daemon_log("ERROR: gosa-si-server could not load module $file", 1);
400             daemon_log("$@", 5);
401                 } else {
402                         my $info = eval($mod_name.'::get_module_info()');
403                         # Only load module if get_module_info() returns a non-null object
404                         if( $info ) {
405                                 my ($input_address, $input_key, $input, $input_active, $input_type) = @{$info};
406                                 $known_modules->{$mod_name} = $info;
407                                 daemon_log("INFO: module $mod_name loaded", 5);
408                         }
409                 }
410     }   
411     close (DIR);
415 #===  FUNCTION  ================================================================
416 #         NAME:  sig_int_handler
417 #   PARAMETERS:  signal - string - signal arose from system
418 #      RETURNS:  noting
419 #  DESCRIPTION:  handels tasks to be done befor signal becomes active
420 #===============================================================================
421 sub sig_int_handler {
422     my ($signal) = @_;
424         if(defined($ldap_handle)) {
425                 $ldap_handle->disconnect;
426         }
428     daemon_log("shutting down gosa-si-server", 1);
429     system("killall gosa-si-server");
431 $SIG{INT} = \&sig_int_handler;
434 sub check_key_and_xml_validity {
435     my ($crypted_msg, $module_key, $session_id) = @_;
436     my $msg;
437     my $msg_hash;
438     my $error_string;
439     eval{
440         $msg = &decrypt_msg($crypted_msg, $module_key);
442         if ($msg =~ /<xml>/i){
443             $msg =~ s/\s+/ /g;  # just for better daemon_log
444             daemon_log("$session_id DEBUG: decrypted_msg: \n$msg", 8);
445             $msg_hash = $xml->XMLin($msg, ForceArray=>1);
447             ##############
448             # check header
449             if( not exists $msg_hash->{'header'} ) { die "no header specified"; }
450             my $header_l = $msg_hash->{'header'};
451             if( 1 > @{$header_l} ) { die 'empty header tag'; }
452             if( 1 < @{$header_l} ) { die 'more than one header specified'; }
453             my $header = @{$header_l}[0];
454             if( 0 == length $header) { die 'empty string in header tag'; }
456             ##############
457             # check source
458             if( not exists $msg_hash->{'source'} ) { die "no source specified"; }
459             my $source_l = $msg_hash->{'source'};
460             if( 1 > @{$source_l} ) { die 'empty source tag'; }
461             if( 1 < @{$source_l} ) { die 'more than one source specified'; }
462             my $source = @{$source_l}[0];
463             if( 0 == length $source) { die 'source error'; }
465             ##############
466             # check target
467             if( not exists $msg_hash->{'target'} ) { die "no target specified"; }
468             my $target_l = $msg_hash->{'target'};
469             if( 1 > @{$target_l} ) { die 'empty target tag'; }
470         }
471     };
472     if($@) {
473         daemon_log("$session_id DEBUG: do not understand the message: $@", 7);
474         $msg = undef;
475         $msg_hash = undef;
476     }
478     return ($msg, $msg_hash);
482 sub check_outgoing_xml_validity {
483     my ($msg) = @_;
485     my $msg_hash;
486     eval{
487         $msg_hash = $xml->XMLin($msg, ForceArray=>1);
489         ##############
490         # check header
491         my $header_l = $msg_hash->{'header'};
492         if( 1 != @{$header_l} ) {
493             die 'no or more than one headers specified';
494         }
495         my $header = @{$header_l}[0];
496         if( 0 == length $header) {
497             die 'header has length 0';
498         }
500         ##############
501         # check source
502         my $source_l = $msg_hash->{'source'};
503         if( 1 != @{$source_l} ) {
504             die 'no or more than 1 sources specified';
505         }
506         my $source = @{$source_l}[0];
507         if( 0 == length $source) {
508             die 'source has length 0';
509         }
510         unless( $source =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
511                 $source =~ /^GOSA$/i ) {
512             die "source '$source' is neither a complete ip-address with port nor 'GOSA'";
513         }
514         
515         ##############
516         # check target  
517         my $target_l = $msg_hash->{'target'};
518         if( 0 == @{$target_l} ) {
519             die "no targets specified";
520         }
521         foreach my $target (@$target_l) {
522             if( 0 == length $target) {
523                 die "target has length 0";
524             }
525             unless( $target =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
526                     $target =~ /^GOSA$/i ||
527                     $target =~ /^\*$/ ||
528                     $target =~ /KNOWN_SERVER/i ||
529                     $target =~ /JOBDB/i ||
530                     $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 ){
531                 die "target '$target' is not a complete ip-address with port or a valid target name or a mac-address";
532             }
533         }
534     };
535     if($@) {
536         daemon_log("WARNING: outgoing msg is not gosa-si envelope conform", 5);
537         daemon_log("$@ ".(defined($msg) && length($msg)>0)?$msg:"Empty Message", 8);
538         $msg_hash = undef;
539     }
541     return ($msg_hash);
545 sub input_from_known_server {
546     my ($input, $remote_ip, $session_id) = @_ ;  
547     my ($msg, $msg_hash, $module);
549     my $sql_statement= "SELECT * FROM known_server";
550     my $query_res = $known_server_db->select_dbentry( $sql_statement ); 
552     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
553         my $host_name = $hit->{hostname};
554         if( not $host_name =~ "^$remote_ip") {
555             next;
556         }
557         my $host_key = $hit->{hostkey};
558         daemon_log("$session_id DEBUG: input_from_known_server: host_name: $host_name", 7);
559         daemon_log("DEBUG: input_from_known_server: host_key: $host_key", 7);
561         # check if module can open msg envelope with module key
562         my ($tmp_msg, $tmp_msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
563         if( (!$tmp_msg) || (!$tmp_msg_hash) ) {
564             daemon_log("$session_id DEBUG: input_from_known_server: deciphering raise error", 7);
565             daemon_log("$@", 8);
566             next;
567         }
568         else {
569             $msg = $tmp_msg;
570             $msg_hash = $tmp_msg_hash;
571             $module = "SIPackages";
572             last;
573         }
574     }
576     if( (!$msg) || (!$msg_hash) || (!$module) ) {
577         daemon_log("$session_id DEBUG: Incoming message is not from a known server", 7);
578     }
579   
580     return ($msg, $msg_hash, $module);
584 sub input_from_known_client {
585     my ($input, $remote_ip, $session_id) = @_ ;  
586     my ($msg, $msg_hash, $module);
588     my $sql_statement= "SELECT * FROM known_clients";
589     my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
590     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
591         my $host_name = $hit->{hostname};
592         if( not $host_name =~ /^$remote_ip:\d*$/) {
593                 next;
594                 }
595         my $host_key = $hit->{hostkey};
596         &daemon_log("$session_id DEBUG: input_from_known_client: host_name: $host_name", 7);
597         &daemon_log("$session_id DEBUG: input_from_known_client: host_key: $host_key", 7);
599         # check if module can open msg envelope with module key
600         ($msg, $msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
602         if( (!$msg) || (!$msg_hash) ) {
603             &daemon_log("$session_id DEGUG: input_from_known_client: deciphering raise error", 7);
604             &daemon_log("$@", 8);
605             next;
606         }
607         else {
608             $module = "SIPackages";
609             last;
610         }
611     }
613     if( (!$msg) || (!$msg_hash) || (!$module) ) {
614         &daemon_log("$session_id DEBUG: Incoming message is not from a known client", 7);
615     }
617     return ($msg, $msg_hash, $module);
621 sub input_from_unknown_host {
622     no strict "refs";
623     my ($input, $session_id) = @_ ;
624     my ($msg, $msg_hash, $module);
625     my $error_string;
626     
627         my %act_modules = %$known_modules;
629         while( my ($mod, $info) = each(%act_modules)) {
631         # check a key exists for this module
632         my $module_key = ${$mod."_key"};
633         if( not defined $module_key ) {
634             if( $mod eq 'ArpHandler' ) {
635                 next;
636             }
637             daemon_log("$session_id ERROR: no key specified in config file for $mod", 1);
638             next;
639         }
640         daemon_log("$session_id DEBUG: $mod: $module_key", 7);
642         # check if module can open msg envelope with module key
643         ($msg, $msg_hash) = &check_key_and_xml_validity($input, $module_key, $session_id);
644         if( (not defined $msg) || (not defined $msg_hash) ) {
645             next;
646         }
647         else {
648             $module = $mod;
649             last;
650         }
651     }
653     if( (!$msg) || (!$msg_hash) || (!$module)) {
654         daemon_log("$session_id DEBUG: Incoming message is not from an unknown host", 7);
655     }
657     return ($msg, $msg_hash, $module);
661 sub create_ciphering {
662     my ($passwd) = @_;
663         if((!defined($passwd)) || length($passwd)==0) {
664                 $passwd = "";
665         }
666     $passwd = substr(md5_hex("$passwd") x 32, 0, 32);
667     my $iv = substr(md5_hex('GONICUS GmbH'),0, 16);
668     my $my_cipher = Crypt::Rijndael->new($passwd , Crypt::Rijndael::MODE_CBC());
669     $my_cipher->set_iv($iv);
670     return $my_cipher;
674 sub encrypt_msg {
675     my ($msg, $key) = @_;
676     my $my_cipher = &create_ciphering($key);
677     my $len;
678     {
679             use bytes;
680             $len= 16-length($msg)%16;
681     }
682     $msg = "\0"x($len).$msg;
683     $msg = $my_cipher->encrypt($msg);
684     chomp($msg = &encode_base64($msg));
685     # there are no newlines allowed inside msg
686     $msg=~ s/\n//g;
687     return $msg;
691 sub decrypt_msg {
693     my ($msg, $key) = @_ ;
694     $msg = &decode_base64($msg);
695     my $my_cipher = &create_ciphering($key);
696     $msg = $my_cipher->decrypt($msg); 
697     $msg =~ s/\0*//g;
698     return $msg;
702 sub get_encrypt_key {
703     my ($target) = @_ ;
704     my $encrypt_key;
705     my $error = 0;
707     # target can be in known_server
708     if( not defined $encrypt_key ) {
709         my $sql_statement= "SELECT * FROM known_server WHERE hostname='$target'";
710         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( $host_name ne $target ) {
714                 next;
715             }
716             $encrypt_key = $hit->{hostkey};
717             last;
718         }
719     }
721     # target can be in known_client
722     if( not defined $encrypt_key ) {
723         my $sql_statement= "SELECT * FROM known_clients WHERE hostname='$target'";
724         my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
725         while( my ($hit_num, $hit) = each %{ $query_res } ) {    
726             my $host_name = $hit->{hostname};
727             if( $host_name ne $target ) {
728                 next;
729             }
730             $encrypt_key = $hit->{hostkey};
731             last;
732         }
733     }
735     return $encrypt_key;
739 #===  FUNCTION  ================================================================
740 #         NAME:  open_socket
741 #   PARAMETERS:  PeerAddr string something like 192.168.1.1 or 192.168.1.1:10000
742 #                [PeerPort] string necessary if port not appended by PeerAddr
743 #      RETURNS:  socket IO::Socket::INET
744 #  DESCRIPTION:  open a socket to PeerAddr
745 #===============================================================================
746 sub open_socket {
747     my ($PeerAddr, $PeerPort) = @_ ;
748     if(defined($PeerPort)){
749         $PeerAddr = $PeerAddr.":".$PeerPort;
750     }
751     my $socket;
752     $socket = new IO::Socket::INET(PeerAddr => $PeerAddr,
753             Porto => "tcp",
754             Type => SOCK_STREAM,
755             Timeout => 5,
756             );
757     if(not defined $socket) {
758         return;
759     }
760 #    &daemon_log("DEBUG: open_socket: $PeerAddr", 7);
761     return $socket;
765 #===  FUNCTION  ================================================================
766 #         NAME:  get_ip 
767 #   PARAMETERS:  interface name (i.e. eth0)
768 #      RETURNS:  (ip address) 
769 #  DESCRIPTION:  Uses ioctl to get ip address directly from system.
770 #===============================================================================
771 sub get_ip {
772         my $ifreq= shift;
773         my $result= "";
774         my $SIOCGIFADDR= 0x8915;       # man 2 ioctl_list
775         my $proto= getprotobyname('ip');
777         socket SOCKET, PF_INET, SOCK_DGRAM, $proto
778                 or die "socket: $!";
780         if(ioctl SOCKET, $SIOCGIFADDR, $ifreq) {
781                 my ($if, $sin)    = unpack 'a16 a16', $ifreq;
782                 my ($port, $addr) = sockaddr_in $sin;
783                 my $ip            = inet_ntoa $addr;
785                 if ($ip && length($ip) > 0) {
786                         $result = $ip;
787                 }
788         }
790         return $result;
794 sub get_local_ip_for_remote_ip {
795         my $remote_ip= shift;
796         my $result="0.0.0.0";
798         if($remote_ip =~ /^(\d\d?\d?\.){3}\d\d?\d?$/) {
799                 if($remote_ip eq "127.0.0.1") {
800                         $result = "127.0.0.1";
801                 } else {
802                         my $PROC_NET_ROUTE= ('/proc/net/route');
804                         open(PROC_NET_ROUTE, "<$PROC_NET_ROUTE")
805                                 or die "Could not open $PROC_NET_ROUTE";
807                         my @ifs = <PROC_NET_ROUTE>;
809                         close(PROC_NET_ROUTE);
811                         # Eat header line
812                         shift @ifs;
813                         chomp @ifs;
814                         foreach my $line(@ifs) {
815                                 my ($Iface,$Destination,$Gateway,$Flags,$RefCnt,$Use,$Metric,$Mask,$MTU,$Window,$IRTT)=split(/\s/, $line);
816                                 my $destination;
817                                 my $mask;
818                                 my ($d,$c,$b,$a)=unpack('a2 a2 a2 a2', $Destination);
819                                 $destination= sprintf("%d.%d.%d.%d", hex($a), hex($b), hex($c), hex($d));
820                                 ($d,$c,$b,$a)=unpack('a2 a2 a2 a2', $Mask);
821                                 $mask= sprintf("%d.%d.%d.%d", hex($a), hex($b), hex($c), hex($d));
822                                 if(new NetAddr::IP($remote_ip)->within(new NetAddr::IP($destination, $mask))) {
823                                         # destination matches route, save mac and exit
824                                         $result= &get_ip($Iface);
825                                         last;
826                                 }
827                         }
828                 }
829         } else {
830                 daemon_log("get_local_ip_for_remote_ip was called with a non-ip parameter: $remote_ip", 1);
831         }
832         return $result;
836 sub send_msg_to_target {
837     my ($msg, $address, $encrypt_key, $msg_header, $session_id) = @_ ;
838     my $error = 0;
839     my $header;
840     my $new_status;
841     my $act_status;
842     my ($sql_statement, $res);
843   
844     if( $msg_header ) {
845         $header = "'$msg_header'-";
846     } else {
847         $header = "";
848     }
850         # Patch the source ip
851         if($msg =~ /<source>0\.0\.0\.0:\d*?<\/source>/) {
852                 my $remote_ip = &get_local_ip_for_remote_ip(sprintf("%s", $address =~ /^([0-9\.]*?):.*$/));
853                 $msg =~ s/<source>(0\.0\.0\.0):(\d*?)<\/source>/<source>$remote_ip:$2<\/source>/s;
854         }
856     # encrypt xml msg
857     my $crypted_msg = &encrypt_msg($msg, $encrypt_key);
859     # opensocket
860     my $socket = &open_socket($address);
861     if( !$socket ) {
862         daemon_log("$session_id ERROR: cannot send ".$header."msg to $address , host not reachable", 1);
863         $error++;
864     }
865     
866     if( $error == 0 ) {
867         # send xml msg
868         print $socket $crypted_msg."\n";
870         daemon_log("$session_id INFO: send ".$header."msg to $address", 5);
871         #daemon_log("DEBUG: message:\n$msg", 9);
872         
873     }
875     # close socket in any case
876     if( $socket ) {
877         close $socket;
878     }
880     if( $error > 0 ) { $new_status = "down"; }
881     else { $new_status = $msg_header; }
884     # known_clients
885     $sql_statement = "SELECT * FROM known_clients WHERE hostname='$address'";
886     $res = $known_clients_db->select_dbentry($sql_statement);
887     if( keys(%$res) > 0) {
888         $act_status = $res->{1}->{'status'};
889         if( $act_status eq "down" ) {
890             $sql_statement = "DELETE FROM known_clients WHERE hostname='$address'";
891             $res = $known_clients_db->del_dbentry($sql_statement);
892             daemon_log("$session_id WARNING: failed 2x to send msg to host '$address', delete host from known_clients", 3);
893         } else { 
894             $sql_statement = "UPDATE known_clients SET status='$new_status' WHERE hostname='$address'";
895             $res = $known_clients_db->update_dbentry($sql_statement);
896             if($new_status eq "down"){
897                 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
898             } else {
899                 daemon_log("$session_id INFO: set '$address' from status '$act_status' to '$new_status'", 5);
900             }
901         }
902     }
904     # known_server
905     $sql_statement = "SELECT * FROM known_server WHERE hostname='$address'";
906     $res = $known_server_db->select_dbentry($sql_statement);
907     if( keys(%$res) > 0 ) {
908         $act_status = $res->{1}->{'status'};
909         if( $act_status eq "down" ) {
910             $sql_statement = "DELETE FROM known_server WHERE hostname='$address'";
911             $res = $known_server_db->del_dbentry($sql_statement);
912             daemon_log("$session_id WARNING: failed 2x to a send msg to host '$address', delete host from known_server", 3);
913         } 
914         else { 
915             $sql_statement = "UPDATE known_server SET status='$new_status' WHERE hostname='$address'";
916             $res = $known_server_db->update_dbentry($sql_statement);
917             if($new_status eq "down"){
918                 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
919             }
920             else {
921                 daemon_log("$session_id INFO: set '$address' from status '$act_status' to '$new_status'", 5);
922             }
923         }
924     }
925     return $error; 
929 sub update_jobdb_status_for_send_msgs {
930     my ($answer, $error) = @_;
931     if( $answer =~ /<jobdb_id>(\d+)<\/jobdb_id>/ ) {
932         my $jobdb_id = $1;
933             
934         # sending msg faild
935         if( $error ) {
936             if (not $answer =~ /<header>trigger_action_reinstall<\/header>/) {
937                 my $sql_statement = "UPDATE $job_queue_tn ".
938                     "SET status='error', result='can not deliver msg, please consult log file' ".
939                     "WHERE id='$jobdb_id'";
940                 my $res = $job_db->update_dbentry($sql_statement);
941             }
943         # sending msg was successful
944         } else {
945             my $sql_statement = "UPDATE $job_queue_tn ".
946                 "SET status='done' ".
947                 "WHERE id='$jobdb_id' AND status='processed'";
948             my $res = $job_db->update_dbentry($sql_statement);
949         }
950     }
953 sub _start {
954     my ($kernel) = $_[KERNEL];
955     &trigger_db_loop($kernel);
956         $kernel->yield('create_fai_server_db', $fai_server_tn );
957         $kernel->yield('create_fai_release_db', $fai_release_tn );
958         $kernel->sig(USR1 => "sig_handler");
961 sub sig_handler {
962         my ($kernel, $signal) = @_[KERNEL, ARG0] ;
963         daemon_log("0 INFO got signal '$signal'", 1); 
964         $kernel->sig_handled();
965         return;
968 sub next_task {
969     my ($session, $heap) = @_[SESSION, HEAP];
971     while ( keys( %{ $heap->{task} } ) < $max_children ) {
972         my $next_task = shift @tasks;
973         last unless defined $next_task;
975         my $task = POE::Wheel::Run->new(
976                 Program => sub { process_task($session, $heap, $next_task) },
977                 StdioFilter => POE::Filter::Reference->new(),
978                 StdoutEvent  => "task_result",
979                 StderrEvent  => "task_debug",
980                 CloseEvent   => "task_done",
981                );
983         $heap->{task}->{ $task->ID } = $task;
984     }
987 sub handle_task_result {
988     my ($kernel, $heap, $result) = @_[KERNEL, HEAP, ARG0];
989     my $client_answer = $result->{'answer'};
990     if( $client_answer =~ s/session_id=(\d+)$// ) {
991         my $session_id = $1;
992         if( defined $session_id ) {
993             my $session_reference = $kernel->ID_id_to_session($session_id);
994             if( defined $session_reference ) {
995                 $heap = $session_reference->get_heap();
996             }
997         }
999         if(exists $heap->{'client'}) {
1000             $heap->{'client'}->put($client_answer);
1001         }
1002     }
1003     $kernel->sig(CHLD => "child_reap");
1006 sub handle_task_debug {
1007     my $result = $_[ARG0];
1008     print STDERR "$result\n";
1011 sub handle_task_done {
1012     my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
1013     delete $heap->{task}->{$task_id};
1014     $kernel->yield("next_task");
1017 sub process_task {
1018     no strict "refs";
1019     my ($session, $heap, $input) = @_;
1020     my $session_id = $session->ID;
1021     my ($msg, $msg_hash, $module);
1022     my $error = 0;
1023     my $answer_l;
1024     my ($answer_header, @answer_target_l, $answer_source);
1025     my $client_answer = "";
1027     daemon_log("", 5); 
1028     daemon_log("$session_id INFO: Incoming msg with session ID $session_id from '".$heap->{'remote_ip'}."'", 5);
1029     daemon_log("$session_id DEBUG: Incoming msg:\n$input", 9);
1031     ####################
1032     # check incoming msg
1033     # msg is from a new client or gosa
1034     ($msg, $msg_hash, $module) = &input_from_unknown_host($input, $session_id);
1035     # msg is from a gosa-si-server or gosa-si-bus
1036     if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1037         ($msg, $msg_hash, $module) = &input_from_known_server($input, $heap->{'remote_ip'}, $session_id);
1038     }
1039     # msg is from a gosa-si-client
1040     if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1041         ($msg, $msg_hash, $module) = &input_from_known_client($input, $heap->{'remote_ip'}, $session_id);
1042     }
1043     # an error occurred
1044     if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1045         # if an incoming msg could not be decrypted (maybe a wrong key), send client a ping. If the client
1046         # could not understand a msg from its server the client cause a re-registering process
1047         my $sql_statement = "SELECT * FROM $main::known_clients_tn WHERE (hostname LIKE '".$heap->{'remote_ip'}."%')";
1048         my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
1049         while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1050             my $host_name = $hit->{'hostname'};
1051             my $host_key = $hit->{'hostkey'};
1052             my $ping_msg = "<xml> <header>gosa_ping</header> <source>$server_address</source> <target>$host_name</target></xml>";
1053             my $error = &send_msg_to_target($ping_msg, $host_name, $host_key, "gosa_ping", $session_id);
1054             &update_jobdb_status_for_send_msgs($ping_msg, $error);
1055         }
1056         $error++;
1057     }
1059     ######################
1060     # process incoming msg
1061     if( $error == 0) {
1062         daemon_log("$session_id INFO: Incoming msg with header '".@{$msg_hash->{'header'}}[0]."'", 5); 
1063         daemon_log("$session_id DEBUG: Processing module ".$module, 7);
1064         $answer_l = &{ $module."::process_incoming_msg" }($msg, $msg_hash, $session_id);
1066         if ( 0 < @{$answer_l} ) {
1067             my $answer_str = join("\n", @{$answer_l});
1068             daemon_log("$session_id DEBUG: $module: Got answer from module: \n".$answer_str,8);
1069         }
1070     }
1071     if( !$answer_l ) { $error++ };
1073     ########
1074     # answer
1075     if( $error == 0 ) {
1077         foreach my $answer ( @{$answer_l} ) {
1078             # for each answer in answer list
1079             
1080             # check outgoing msg to xml validity
1081             my $answer_hash = &check_outgoing_xml_validity($answer);
1082             if( not defined $answer_hash ) {
1083                 next;
1084             }
1085             
1086             $answer_header = @{$answer_hash->{'header'}}[0];
1087             @answer_target_l = @{$answer_hash->{'target'}};
1088             $answer_source = @{$answer_hash->{'source'}}[0];
1090             # deliver msg to all targets 
1091             foreach my $answer_target ( @answer_target_l ) {
1093                 # targets of msg are all gosa-si-clients in known_clients_db
1094                 if( $answer_target eq "*" ) {
1095                     # answer is for all clients
1096                     my $sql_statement= "SELECT * FROM known_clients";
1097                     my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
1098                     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1099                         my $host_name = $hit->{hostname};
1100                         my $host_key = $hit->{hostkey};
1101                         my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1102                         &update_jobdb_status_for_send_msgs($answer, $error);
1103                     }
1104                 }
1106                 # targets of msg are all gosa-si-server in known_server_db
1107                 elsif( $answer_target eq "KNOWN_SERVER" ) {
1108                     # answer is for all server in known_server
1109                     my $sql_statement= "SELECT * FROM known_server";
1110                     my $query_res = $known_server_db->select_dbentry( $sql_statement ); 
1111                     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1112                         my $host_name = $hit->{hostname};
1113                         my $host_key = $hit->{hostkey};
1114                         $answer =~ s/KNOWN_SERVER/$host_name/g;
1115                         my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1116                         &update_jobdb_status_for_send_msgs($answer, $error);
1117                     }
1118                 }
1120                 # target of msg is GOsa
1121                                 elsif( $answer_target eq "GOSA" ) {
1122                                         my $session_id = ($1) if $answer =~ /<session_id>(\d+?)<\/session_id>/;
1123                                         my $add_on = "";
1124                     if( defined $session_id ) {
1125                         $add_on = ".session_id=$session_id";
1126                     }
1127                     # answer is for GOSA and has to returned to connected client
1128                     my $gosa_answer = &encrypt_msg($answer, $GosaPackages_key);
1129                     $client_answer = $gosa_answer.$add_on;
1130                 }
1132                 # target of msg is job queue at this host
1133                 elsif( $answer_target eq "JOBDB") {
1134                     $answer =~ /<header>(\S+)<\/header>/;   
1135                     my $header;
1136                     if( defined $1 ) { $header = $1; }
1137                     my $error = &send_msg_to_target($answer, $server_address, $GosaPackages_key, $header, $session_id);
1138                     &update_jobdb_status_for_send_msgs($answer, $error);
1139                 }
1141                 # target of msg is a mac address
1142                 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 ) {
1143                     daemon_log("$session_id INFO: target is mac address '$answer_target', looking for host in known_clients", 5);
1144                     my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$answer_target'";
1145                     my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1146                     my $found_ip_flag = 0;
1147                     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1148                         my $host_name = $hit->{hostname};
1149                         my $host_key = $hit->{hostkey};
1150                         $answer =~ s/$answer_target/$host_name/g;
1151                         daemon_log("$session_id INFO: found host '$host_name', associated to '$answer_target'", 5);
1152                         my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1153                         &update_jobdb_status_for_send_msgs($answer, $error);
1154                         $found_ip_flag++ ;
1155                     }   
1156                     if( $found_ip_flag == 0) {
1157                         daemon_log("$session_id WARNING: no host found in known_clients with mac address '$answer_target'", 3);
1158                         if( $bus_activ eq "true" ) { 
1159                             daemon_log("$session_id INFO: try to forward msg '$answer_header' to bus '$bus_address'", 5);
1160                             my $sql_statement = "SELECT * FROM known_server WHERE hostname='$bus_address'";
1161                             my $query_res = $known_server_db->select_dbentry( $sql_statement );
1162                             while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1163                                 my $bus_address = $hit->{hostname};
1164                                 my $bus_key = $hit->{hostkey};
1165                                 my $error = &send_msg_to_target($answer, $bus_address, $bus_key, $answer_header, $session_id);
1166                                 &update_jobdb_status_for_send_msgs($answer, $error);
1167                                 last;
1168                             }
1169                         }
1171                     }
1173                 #  answer is for one specific host   
1174                 } else {
1175                     # get encrypt_key
1176                     my $encrypt_key = &get_encrypt_key($answer_target);
1177                     if( not defined $encrypt_key ) {
1178                         # unknown target, forward msg to bus
1179                         daemon_log("$session_id WARNING: unknown target '$answer_target'", 3);
1180                         if( $bus_activ eq "true" ) { 
1181                             daemon_log("$session_id INFO: try to forward msg '$answer_header' to bus '$bus_address'", 5);
1182                             my $sql_statement = "SELECT * FROM known_server WHERE hostname='$bus_address'";
1183                             my $query_res = $known_server_db->select_dbentry( $sql_statement );
1184                             my $res_length = keys( %{$query_res} );
1185                             if( $res_length == 0 ){
1186                                 daemon_log("$session_id WARNING: send '$answer_header' to '$bus_address' failed, ".
1187                                         "no bus found in known_server", 3);
1188                             }
1189                             else {
1190                                 while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1191                                     my $bus_key = $hit->{hostkey};
1192                                     my $error = &send_msg_to_target($answer, $bus_address, $bus_key, $answer_header,$session_id );
1193                                     &update_jobdb_status_for_send_msgs($answer, $error);
1194                                 }
1195                             }
1196                         }
1197                         next;
1198                     }
1199                     my $error = &send_msg_to_target($answer, $answer_target, $encrypt_key, $answer_header,$session_id);
1200                     &update_jobdb_status_for_send_msgs($answer, $error);
1201                 }
1202             }
1203         }
1204     }
1206     my $filter = POE::Filter::Reference->new();
1207     my %result = ( 
1208             status => "seems ok to me",
1209             answer => $client_answer,
1210             );
1212     my $output = $filter->put( [ \%result ] );
1213     print @$output;
1219 sub trigger_db_loop {
1220         my ($kernel) = @_ ;
1221         $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1222     $kernel->delay_set('watch_for_done_jobs', $job_queue_loop_delay); 
1225 sub watch_for_done_jobs {
1226     my ($kernel,$heap) = @_[KERNEL, HEAP];
1228     my $sql_statement = "SELECT * FROM ".$job_queue_tn.
1229         " WHERE status='done'";
1230         my $res = $job_db->select_dbentry( $sql_statement );
1232     while( my ($id, $hit) = each %{$res} ) {
1233         my $jobdb_id = $hit->{id};
1234         my $sql_statement = "DELETE FROM $job_queue_tn WHERE id='$jobdb_id'"; 
1235         my $res = $job_db->del_dbentry($sql_statement);
1236     }
1238     $kernel->delay_set('watch_for_done_jobs',$job_queue_loop_delay);
1241 sub watch_for_new_jobs {
1242         my ($kernel,$heap) = @_[KERNEL, HEAP];
1244         # check gosa job queue for jobs with executable timestamp
1245     my $timestamp = &get_time();
1246     my $sql_statement = "SELECT * FROM ".$job_queue_tn.
1247         " WHERE status='waiting' AND timestamp<'$timestamp'";
1248         my $res = $job_db->select_dbentry( $sql_statement );
1250         while( my ($id, $hit) = each %{$res} ) {         
1251                 my $jobdb_id = $hit->{id};
1252                 my $macaddress = $hit->{'macaddress'};
1253         my $job_msg = $hit->{'xmlmessage'};
1254         daemon_log("J DEBUG: its time to execute $job_msg", 7); 
1255         my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$macaddress'";
1256                 my $res_hash = $known_clients_db->select_dbentry( $sql_statement );
1257                 # expect macaddress is unique!!!!!!
1258                 my $target = $res_hash->{1}->{hostname};
1260 #        if (not defined $target) {
1261 #                       &daemon_log("ERROR: no host found for mac address: $macaddress", 1);
1262 #                       &daemon_log("$hit->{xmlmessage}", 8);
1263 #            my $sql_statement = "UPDATE $job_queue_tn ".
1264 #                "SET status='error', result='no host found for mac address' ".
1265 #                "WHERE id='$jobdb_id'";
1266 #                       my $res = $job_db->update_dbentry($sql_statement);
1267 #                       next;
1268 #               }
1270                 # change header
1271         $job_msg =~ s/<header>job_/<header>gosa_/;
1273                 # add sqlite_id 
1274         $job_msg =~ s/<\/xml>$/<jobdb_id>$jobdb_id<\/jobdb_id><\/xml>/;
1276         $job_msg =~ /<header>(\S+)<\/header>/;
1277         my $header = $1 ;
1278                 my $func_error = &send_msg_to_target($job_msg, $server_address, $GosaPackages_key, $header, "J");
1280         # update status in job queue to 'processing'
1281         $sql_statement = "UPDATE $job_queue_tn SET status='processing' WHERE id='$jobdb_id'";
1282         my $res = $job_db->update_dbentry($sql_statement);
1283     }
1285         $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1289 sub refresh_ldap_handle {
1290   my $mesg;
1292   daemon_log("DEBUG: Trying to create a connection to URI $ldap_uri", 5);
1293   # Get an ldap handle, if we don't have one
1294   if( ! defined $ldap_handle ){
1295           $ldap_handle = Net::LDAP->new( $ldap_uri );
1296   }
1297   # Still not defined?
1298   if( ! defined $ldap_handle ) {
1299           daemon_log( "ch $$: Net::LDAP constructor failed: $!\n" );
1300           return 0;
1301   }
1303   # Bind to ldap server - eventually authenticate
1304   if( defined $ldap_admin_dn ) {
1305     if( defined $ldap_admin_password ) {
1306       $mesg = $ldap_handle->bind( $ldap_admin_dn, password => $ldap_admin_password );
1307     } else {
1308       $mesg = $ldap_handle->bind( $ldap_admin_dn );
1309     }
1310   } else {
1311     $mesg = $ldap_handle->bind();
1312   }
1314   if( 0 != $mesg->code ) {
1315     undef( $ldap_handle ) if( 81 == $mesg->code );
1316     daemon_log( "ch $$: LDAP bind: error (". $mesg->code . ') - ' . $mesg->error . "\n", 1);
1317     return 0;
1318   }
1320   return 1;
1324 sub change_fai_state {
1325     my ($st, $targets, $session_id) = @_;
1326     $session_id = 0 if not defined $session_id;
1327     # Set FAI state to localboot
1328     my %mapActions= (
1329         reboot    => '',
1330         update    => 'softupdate',
1331         localboot => 'localboot',
1332         reinstall => 'install',
1333         rescan    => '',
1334         wake      => '',
1335         memcheck  => 'memcheck',
1336         sysinfo   => 'sysinfo',
1337     );
1339     # Return if this is unknown
1340     if (!exists $mapActions{ $st }){
1341         daemon_log("$session_id ERROR: unknown action '$st', can not translate ot FAIstate", 1); 
1342       return;
1343     }
1345     my $state= $mapActions{ $st };
1347     &refresh_ldap_handle();
1348     if( defined($ldap_handle) ) {
1350       # Build search filter for hosts
1351         my $search= "(&(objectClass=GOhard)";
1352         foreach (@{$targets}){
1353             $search.= "(macAddress=$_)";
1354         }
1355         $search.= ")";
1357       # If there's any host inside of the search string, procress them
1358         if (!($search =~ /macAddress/)){
1359             daemon_log("$session_id ERROR: no macAddress found in filter statement for LDAP search: '$search'", 1);    
1360             return;
1361         }
1363       # Perform search for Unit Tag
1364       my $mesg = $ldap_handle->search(
1365           base   => $ldap_base,
1366           scope  => 'sub',
1367           attrs  => ['dn', 'FAIstate', 'objectClass'],
1368           filter => "$search"
1369           );
1371       if ($mesg->count) {
1372         my @entries = $mesg->entries;
1373         foreach my $entry (@entries) {
1374           # Only modify entry if it is not set to '$state'
1375           if ($entry->get_value("FAIstate") ne "$state"){
1376             daemon_log("$session_id INFO: Setting FAIstate to '$state' for ".$entry->dn, 5);
1377             my $result;
1378             my %tmp = map { $_ => 1 } $entry->get_value("objectClass");
1379             if (exists $tmp{'FAIobject'}){
1380               if ($state eq ''){
1381                 $result= $ldap_handle->modify($entry->dn, changes => [
1382                             delete => [ FAIstate => [] ] ]);
1383               } else {
1384                 $result= $ldap_handle->modify($entry->dn, changes => [
1385                             replace => [ FAIstate => $state ] ]);
1386               }
1387             } elsif ($state ne ''){
1388               $result= $ldap_handle->modify($entry->dn, changes => [
1389                           add     => [ objectClass => 'FAIobject' ],
1390                           add     => [ FAIstate => $state ] ]);
1391             }
1393             # Errors?
1394             if ($result->code){
1395               daemon_log("$session_id Error: Setting FAIstate to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
1396             }
1398           } else {
1399             daemon_log("$session_id DEBUG FAIstate at host found with filter statement '$search' already at state '$st'", 7); 
1400           }  
1401         }
1402       }
1403     # if no ldap handle defined
1404     } else {
1405         daemon_log("$session_id ERROR: no LDAP handle defined for update FAIstate", 1); 
1406     }
1410 sub change_goto_state {
1411     my ($st, $targets, $session_id) = @_;
1412     $session_id = 0  if not defined $session_id;
1414     # Switch on or off?
1415     my $state= $st eq 'active' ? 'active': 'locked';
1417     &refresh_ldap_handle();
1418     if( defined($ldap_handle) ) {
1420       # Build search filter for hosts
1421       my $search= "(&(objectClass=GOhard)";
1422       foreach (@{$targets}){
1423         $search.= "(macAddress=$_)";
1424       }
1425       $search.= ")";
1427       # If there's any host inside of the search string, procress them
1428       if (!($search =~ /macAddress/)){
1429         return;
1430       }
1432       # Perform search for Unit Tag
1433       my $mesg = $ldap_handle->search(
1434           base   => $ldap_base,
1435           scope  => 'sub',
1436           attrs  => ['dn', 'gotoMode'],
1437           filter => "$search"
1438           );
1440       if ($mesg->count) {
1441         my @entries = $mesg->entries;
1442         foreach my $entry (@entries) {
1444           # Only modify entry if it is not set to '$state'
1445           if ($entry->get_value("gotoMode") ne $state){
1447             daemon_log("$session_id INFO: Setting gotoMode to '$state' for ".$entry->dn, 5);
1448             my $result;
1449             $result= $ldap_handle->modify($entry->dn, changes => [
1450                                                 replace => [ gotoMode => $state ] ]);
1452             # Errors?
1453             if ($result->code){
1454               &daemon_log("$session_id Error: Setting gotoMode to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
1455             }
1457           }
1458         }
1459       }
1461     }
1465 sub create_fai_server_db {
1466     my ($table_name, $kernel) = @_;
1467         my $result;
1469         if(defined($ldap_handle)) {
1470                 daemon_log("INFO: create_fai_server_db: start", 5);
1471                 my $mesg= $ldap_handle->search(
1472                         base   => $ldap_base,
1473                         scope  => 'sub',
1474                         attrs  => ['FAIrepository', 'gosaUnitTag'],
1475                         filter => "(&(FAIrepository=*)(objectClass=FAIrepositoryServer))",
1476                 );
1477                 if($mesg->{'resultCode'} == 0 &&
1478                    $mesg->count != 0) {
1479                    foreach my $entry (@{$mesg->{entries}}) {
1480                            if($entry->exists('FAIrepository')) {
1481                                    # Add an entry for each Repository configured for server
1482                                    foreach my $repo(@{$entry->get_value('FAIrepository', asref => 1)}) {
1483                                                    my($tmp_url,$tmp_server,$tmp_release,$tmp_sections) = split(/\|/, $repo);
1484                                                    my $tmp_tag= $entry->get_value('gosaUnitTag') || "";
1485                                                    $result= $fai_server_db->add_dbentry( { 
1486                                                                    table => $table_name,
1487                                                                    primkey => ['server', 'release', 'tag'],
1488                                                                    server => $tmp_url,
1489                                                                    release => $tmp_release,
1490                                                                    sections => $tmp_sections,
1491                                                                    tag => (length($tmp_tag)>0)?$tmp_tag:"",
1492                                                            } );
1493                                            }
1494                                    }
1495                            }
1496                    }
1497                 daemon_log("INFO: create_fai_server_db: finished", 5);
1499                 # TODO: Find a way to post the 'create_packages_list_db' event
1500                 &create_packages_list_db();
1501         }       
1503         return $result;
1506 sub run_create_fai_server_db {
1507     my ($kernel, $session, $heap, $table_name) = @_[KERNEL, SESSION, HEAP, ARG0];
1508     my $task = POE::Wheel::Run->new(
1509             Program => sub { &create_fai_server_db($table_name,$kernel) },
1510             StdoutEvent  => "session_run_result",
1511             StderrEvent  => "session_run_debug",
1512             CloseEvent   => "session_run_done",
1513             );
1515     $heap->{task}->{ $task->ID } = $task;
1516     return;
1520 sub create_fai_release_db {
1521         my ($table_name) = @_;
1522         my $result;
1524         if(defined($ldap_handle)) {
1525                 daemon_log("INFO: create_fai_release_db: start",5);
1526                 my $mesg= $ldap_handle->search(
1527                         base   => $ldap_base,
1528                         scope  => 'sub',
1529                         attrs  => [],
1530                         filter => "(&(objectClass=organizationalUnit)(ou=fai))",
1531                 );
1532                 if($mesg->{'resultCode'} == 0 &&
1533                         $mesg->count != 0) {
1534                         # Walk through all possible FAI container ou's
1535                         my @sql_list;
1536                         my $timestamp= &get_time();
1537                         foreach my $ou (@{$mesg->{entries}}) {
1538                                 my $tmp_classes= resolve_fai_classes($ou->dn);
1539                                 if(defined($tmp_classes) && ref($tmp_classes) eq 'HASH') {
1540                                         my @tmp_array=get_fai_release_entries($tmp_classes);
1541                                         if(@tmp_array) {
1542                                                 foreach my $entry (@tmp_array) {
1543                                                         if(defined($entry) && ref($entry) eq 'HASH') {
1544                                                                 my $sql= 
1545                                                                 "INSERT INTO $table_name "
1546                                                                 ."(timestamp, release, class, type, state) VALUES ("
1547                                                                 .$timestamp.","
1548                                                                 ."'".$entry->{'release'}."',"
1549                                                                 ."'".$entry->{'class'}."',"
1550                                                                 ."'".$entry->{'type'}."',"
1551                                                                 ."'".$entry->{'state'}."')";
1552                                                                 push @sql_list, $sql;
1553                                                         }
1554                                                 }
1555                                         }
1556                                 }
1557                         }
1558                         daemon_log("DEBUG: Inserting ".scalar @sql_list." entries to DB",6);
1559                         if(@sql_list) {
1560                                 unshift @sql_list, "DELETE FROM $table_name";
1561                                 $fai_server_db->exec_statementlist(\@sql_list);
1562                         }
1563                         daemon_log("DEBUG: Done with inserting",6);
1564                 }
1565                 daemon_log("INFO: create_fai_release_db: finished",5);
1566         }
1568         return $result;
1570 sub run_create_fai_release_db {
1571     my ($session, $heap, $table_name) = @_[SESSION, HEAP, ARG0];
1572     my $task = POE::Wheel::Run->new(
1573             Program => sub { &create_fai_release_db($table_name) },
1574             StdoutEvent  => "session_run_result",
1575             StderrEvent  => "session_run_debug",
1576             CloseEvent   => "session_run_done",
1577             );
1579     $heap->{task}->{ $task->ID } = $task;
1580     return;
1583 sub get_fai_types {
1584         my $tmp_classes = shift || return undef;
1585         my @result;
1587         foreach my $type(keys %{$tmp_classes}) {
1588                 if(defined($tmp_classes->{$type}[0]) && (!($tmp_classes->{$type}[0] =~ /^.*?removed.*?$/))) {
1589                         my $entry = {
1590                                 type => $type,
1591                                 state => $tmp_classes->{$type}[0],
1592                         };
1593                         push @result, $entry;
1594                 }
1595         }
1597         return @result;
1600 sub get_fai_state {
1601         my $result = "";
1602         my $tmp_classes = shift || return $result;
1604         foreach my $type(keys %{$tmp_classes}) {
1605                 if(defined($tmp_classes->{$type}[0])) {
1606                         $result = $tmp_classes->{$type}[0];
1607                         
1608                 # State is equal for all types in class
1609                         last;
1610                 }
1611         }
1613         return $result;
1616 sub resolve_fai_classes {
1617         my $result;
1618         my $fai_base= shift;
1619         my @possible_fai_classes= ("FAIscript", "FAIhook", "FAIpartitionTable", "FAItemplate", "FAIvariable", "FAIprofile", "FAIpackageList");
1620         my $fai_filter= "(|(&(objectClass=FAIclass)(|(objectClass=".join(")(objectClass=", @possible_fai_classes).")))(objectClass=FAIbranch))";
1621         my $fai_classes;
1623         daemon_log("DEBUG: Searching for FAI entries in base $fai_base",6);
1624         my $mesg= $ldap_handle->search(
1625                 base   => $fai_base,
1626                 scope  => 'sub',
1627                 attrs  => ['cn','objectClass','FAIstate'],
1628                 filter => $fai_filter,
1629         );
1630         daemon_log("DEBUG: Found ".$mesg->count()." FAI entries",6);
1632         if($mesg->{'resultCode'} == 0 &&
1633                 $mesg->count != 0) {
1634                 foreach my $entry (@{$mesg->{entries}}) {
1635                         if($entry->exists('cn')) {
1636                                 my $tmp_dn= $entry->dn();
1638                                 # Skip classname and ou dn parts for class
1639                                 my $tmp_release = ($1) if $tmp_dn =~ /^[^,]+,[^,]+,(.*?),$fai_base$/;
1641                                 # Skip classes without releases
1642                                 if((!defined($tmp_release)) || length($tmp_release)==0) {
1643                                         next;
1644                                 }
1646                                 my $tmp_cn= $entry->get_value('cn');
1647                                 my $tmp_state= $entry->get_value('FAIstate');
1649                                 my $tmp_type;
1650                                 # Get FAI type
1651                                 for my $oclass(@{$entry->get_value('objectClass', asref => 1)}) {
1652                                         if(grep $_ eq $oclass, @possible_fai_classes) {
1653                                                 $tmp_type= $oclass;
1654                                                 last;
1655                                         }
1656                                 }
1658                                 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
1659                                         # A Subrelease
1660                                         my @sub_releases = split(/,/, $tmp_release);
1662                                         # Walk through subreleases and build hash tree
1663                                         my $hash;
1664                                         while(my $tmp_sub_release = pop @sub_releases) {
1665                                                 $hash .= "\{'$tmp_sub_release'\}->";                                            
1666                                         }
1667                                         eval('push @{$fai_classes->'.$hash.'{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";');
1668                                 } else {
1669                                         # A branch, no subrelease
1670                                         push @{$fai_classes->{$tmp_release}->{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";
1671                                 }
1672                         } elsif (!$entry->exists('cn')) {
1673                                 my $tmp_dn= $entry->dn();
1674                                 my $tmp_release = ($1) if $tmp_dn =~ /^(.*?),$fai_base$/;
1676                                 # Skip classes without releases
1677                                 if((!defined($tmp_release)) || length($tmp_release)==0) {
1678                                         next;
1679                                 }
1681                                 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
1682                                         # A Subrelease
1683                                         my @sub_releases= split(/,/, $tmp_release);
1685                                         # Walk through subreleases and build hash tree
1686                                         my $hash;
1687                                         while(my $tmp_sub_release = pop @sub_releases) {
1688                                                 $hash .= "\{'$tmp_sub_release'\}->";                                            
1689                                         }
1690                                         # Remove the last two characters
1691                                         chop($hash);
1692                                         chop($hash);
1694                                         eval('$fai_classes->'.$hash.'= {}');
1695                                 } else {
1696                                         # A branch, no subrelease
1697                                         if(!exists($fai_classes->{$tmp_release})) {
1698                                                 $fai_classes->{$tmp_release} = {};
1699                                         }
1700                                 }
1701                         }
1702                 }
1704                 # The hash is complete, now we can honor the copy-on-write based missing entries
1705                 foreach my $release (keys %$fai_classes) {
1706                         $result->{$release}= deep_copy(apply_fai_inheritance($fai_classes->{$release}));
1707                 }
1708         }
1709         return $result;
1712 sub apply_fai_inheritance {
1713        my $fai_classes = shift || return {};
1714        my $tmp_classes;
1716        # Get the classes from the branch
1717        foreach my $class (keys %{$fai_classes}) {
1718                # Skip subreleases
1719                if($class =~ /^ou=.*$/) {
1720                        next;
1721                } else {
1722                        $tmp_classes->{$class}= deep_copy($fai_classes->{$class});
1723                }
1724        }
1726        # Apply to each subrelease
1727        foreach my $subrelease (keys %{$fai_classes}) {
1728                if($subrelease =~ /ou=/) {
1729                        foreach my $tmp_class (keys %{$tmp_classes}) {
1730                                if(!exists($fai_classes->{$subrelease}->{$tmp_class})) {
1731                                        $fai_classes->{$subrelease}->{$tmp_class} =
1732                                        deep_copy($tmp_classes->{$tmp_class});
1733                                } else {
1734                                        foreach my $type (keys %{$tmp_classes->{$tmp_class}}) {
1735                                                if(!exists($fai_classes->{$subrelease}->{$tmp_class}->{$type})) {
1736                                                        $fai_classes->{$subrelease}->{$tmp_class}->{$type}=
1737                                                        deep_copy($tmp_classes->{$tmp_class}->{$type});
1738                                                }
1739                                        }
1740                                }
1741                        }
1742                }
1743        }
1745        # Find subreleases in deeper levels
1746        foreach my $subrelease (keys %{$fai_classes}) {
1747                if($subrelease =~ /ou=/) {
1748                        foreach my $subsubrelease (keys %{$fai_classes->{$subrelease}}) {
1749                                if($subsubrelease =~ /ou=/) {
1750                                        apply_fai_inheritance($fai_classes->{$subrelease});
1751                                }
1752                        }
1753                }
1754        }
1756        return $fai_classes;
1759 sub get_fai_release_entries {
1760         my $tmp_classes = shift || return;
1761         my $parent = shift || "";
1762         my @result = shift || ();
1764         foreach my $entry (keys %{$tmp_classes}) {
1765                 if(defined($entry)) {
1766                         if($entry =~ /^ou=.*$/) {
1767                                 my $release_name = $entry;
1768                                 $release_name =~ s/ou=//g;
1769                                 if(length($parent)>0) {
1770                                         $release_name = $parent."/".$release_name;
1771                                 }
1772                                 my @bufentries = get_fai_release_entries($tmp_classes->{$entry}, $release_name, @result);
1773                                 foreach my $bufentry(@bufentries) {
1774                                         push @result, $bufentry;
1775                                 }
1776                         } else {
1777                                 my @types = get_fai_types($tmp_classes->{$entry});
1778                                 foreach my $type (@types) {
1779                                         push @result, 
1780                                         {
1781                                                 'class' => $entry,
1782                                                 'type' => $type->{'type'},
1783                                                 'release' => $parent,
1784                                                 'state' => $type->{'state'},
1785                                         };
1786                                 }
1787                         }
1788                 }
1789         }
1791         return @result;
1794 sub deep_copy {
1795         my $this = shift;
1796         if (not ref $this) {
1797                 $this;
1798         } elsif (ref $this eq "ARRAY") {
1799                 [map deep_copy($_), @$this];
1800         } elsif (ref $this eq "HASH") {
1801                 +{map { $_ => deep_copy($this->{$_}) } keys %$this};
1802         } else { die "what type is $_?" }
1806 sub session_run_result {
1807     my ($kernel, $heap, $client_answer) = @_[KERNEL, HEAP, ARG0];    
1808     $kernel->sig(CHLD => "child_reap");
1811 sub session_run_debug {
1812     my $result = $_[ARG0];
1813     print STDERR "$result\n";
1816 sub session_run_done {
1817     my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
1818     delete $heap->{task}->{$task_id};
1821 sub create_sources_list {
1822         my $result="/tmp/gosa_si_tmp_sources_list";
1824         # Remove old file
1825         if(stat($result)) {
1826                 unlink($result);
1827         }
1829         my $fh;
1830         open($fh, ">$result") or return undef;
1831         if(defined($ldap_server_dn) and length($ldap_server_dn) > 0) {
1832                 my $mesg=$ldap_handle->search(
1833                                 base    => $ldap_server_dn,
1834                                 scope   => 'base',
1835                                 attrs   => 'FAIrepository',
1836                                 filter  => 'objectClass=FAIrepositoryServer'
1837                                 );
1838                 if($mesg->count) {
1839                         foreach my $entry(@{$mesg->{'entries'}}) {
1840                                 my ($server, $tag, $release, $sections)= split /\|/, $entry->get_value('FAIrepository');
1841                                 my $line = "deb $server $release";
1842                                 $sections =~ s/,/ /g;
1843                                 $line.= " $sections";
1844                                 print $fh $line."\n";
1845                         }
1846                 }
1847         }
1848         close($fh);
1850         return $result;
1853 sub create_packages_list_db {
1854     my ($sources_file) = @_ || &create_sources_list;
1855     my $line;
1856     daemon_log("INFO: create_packages_list_db: start", 5); 
1858     open(CONFIG, "<$sources_file") or do {
1859         daemon_log( "ERROR: create_packages_list_db: Failed to open '$sources_file'", 1);
1860         return;
1861     };
1862     
1863     # Read lines
1864     while ($line = <CONFIG>){
1865         # Unify
1866         chop($line);
1867         $line =~ s/^\s+//;
1868         $line =~ s/^\s+/ /;
1870         # Strip comments
1871         $line =~ s/#.*$//g;
1873         # Skip empty lines
1874         if ($line =~ /^\s*$/){
1875             next;
1876         }
1878         # Interpret deb line
1879         if ($line =~ /^deb [^\s]+\s[^\s]+\s[^\s]+/){
1880             my( $baseurl, $dist, $sections ) = ($line =~ /^deb\s([^\s]+)\s+([^\s]+)\s+(.*)$/);
1881             my $section;
1882             foreach $section (split(' ', $sections)){
1883                 &parse_package_info( $baseurl, $dist, $section );
1884             }
1885         }
1886     }
1888     close (CONFIG);
1890     daemon_log("INFO: create_packages_list_db: finished", 5); 
1891     return;
1894 sub run_create_packages_list_db {
1895     my ($session, $heap) = @_[SESSION, HEAP];
1896     my $task = POE::Wheel::Run->new(
1897             Program => sub {&create_packages_list_db},
1898             StdoutEvent  => "session_run_result",
1899             StderrEvent  => "session_run_debug",
1900             CloseEvent   => "session_run_done",
1901             );
1902     $heap->{task}->{ $task->ID } = $task;
1905 sub parse_package_info {
1906   my ($baseurl, $dist, $section)= @_;
1907   my ($package);
1909   my ($path) = ($baseurl =~ m%://[^/]*(.*)$%);
1910   $repo_dirs{ "${repo_path}/pool" } = 1;
1912   foreach $package ("Packages.gz"){
1913     daemon_log("DEBUG: create_packages_list: fetch $baseurl, $dist, $section", 7);
1914     get_package( "$baseurl/dists/$dist/$section/binary-$arch/$package", "$outdir/$dist/$section" );
1915     parse_package( "$outdir/$dist/$section", $dist, $path );
1916   }
1917   find(\&cleanup_and_extract, keys( %repo_dirs ) );
1920 sub get_package {
1921   my ($url, $dest)= @_;
1923   my $tpath = dirname($dest);
1924   -d "$tpath" || mkpath "$tpath";
1926   # This is ugly, but I've no time to take a look at "how it works in perl"
1927   if(0 == system("wget '$url' -O '$dest' 2>/dev/null") ) {
1928       system("gunzip -cd '$dest' > '$dest.in'");
1929       system("rm -f '$dest'");
1930   } else {
1931       daemon_log("ERROR: create_packages_list_db: get_packages: fetching '$url' failed!", 1);
1932   }
1933   return 0;
1936 sub parse_package {
1937     my ($path, $dist, $srv_path)= @_;
1938     my ($package, $version, $section, $description);
1939     my @sql_list;
1940     my $PACKAGES;
1942     if(not stat("$path.in")) {
1943         daemon_log("ERROR: create_packages_list: parse_package: file '$path.in' is not readable",1);
1944         return;
1945     }
1947     open($PACKAGES, "<$path.in");
1948         if(not defined($PACKAGES)) {
1949         daemon_log("ERROR: create_packages_list_db: parse_package: can not open '$path.in'",1); 
1950         return;
1951     }
1953     # Read lines
1954     while (<$PACKAGES>){
1955         my $line = $_;
1956         # Unify
1957         chop($line);
1959         # Use empty lines as a trigger
1960         if ($line =~ /^\s*$/){
1961             my $sql = "INSERT INTO packages_list VALUES ('$dist', '$package', '$version', '$section', '', 'none', '0')";
1962             push(@sql_list, $sql);
1963             $package = "none";
1964             $version = "none";
1965             $section = "none";
1966             $description = "none"; 
1967             next;
1968         }
1970         # Trigger for package name
1971         if ($line =~ /^Package:\s/){
1972             ($package)= ($line =~ /^Package: (.*)$/);
1973             next;
1974         }
1976         # Trigger for version
1977         if ($line =~ /^Version:\s/){
1978             ($version)= ($line =~ /^Version: (.*)$/);
1979             next;
1980         }
1982         # Trigger for description
1983         if ($line =~ /^Description:\s/){
1984             ($description)= ($line =~ /^Description: (.*)$/);
1985             next;
1986         }
1988         # Trigger for section
1989         if ($line =~ /^Section:\s/){
1990             ($section)= ($line =~ /^Section: (.*)$/);
1991             next;
1992         }
1994         # Trigger for filename
1995         if ($line =~ /^Filename:\s/){
1996                 my ($filename) = ($line =~ /^Filename: (.*)$/);
1997                 store_fileinfo( $package, $filename, $dist, $srv_path, $version, $repo_path );
1998                 next;
1999         }
2000     }
2002     close( $PACKAGES );
2003     unlink( "$path.in" );
2004     
2005     $packages_list_db->exec_statementlist(\@sql_list);
2008 sub store_fileinfo {
2009   my( $package, $file, $dist, $path, $vers, $srvdir) = @_;
2011   my %fileinfo = (
2012     'package' => $package,
2013     'dist' => $dist,
2014     'version' => $vers,
2015   );
2017   $repo_files{ "${srvdir}/$file" } = \%fileinfo;
2020 sub cleanup_and_extract {
2022   my %templates = ();
2023   my $fileinfo = $repo_files{ $File::Find::name };
2025   if( defined $fileinfo ) {
2026   print STDERR "============> $File::Find::name\n"; 
2028     my $dir = "$outdir/$fileinfo->{ 'dist' }/debconf.d";
2029     system( "mkdir -p '$dir'" );
2031     system( "dpkg -e '$File::Find::name' '$dir/DEBIAN'" );
2032     if( -f "$dir/DEBIAN/templates" ) {
2033       my $package = $fileinfo->{ 'package' };
2034       my $newver = $fileinfo->{ 'version' };
2036       daemon_log("DEBUG: Found debconf templates in '$package' - $newver", 5);
2037       if( exists $templates{ "$fileinfo->{ 'dist' }/$package" } ) {
2038         my $oldver = $templates{ "$fileinfo->{ 'dist' }/$package" };
2039         my $ret = system( "dpkg --compare-versions '$oldver' gt '$newver'" );
2040         if( ! $ret ) {
2041           daemon_log("DEBUG: duplicated '$package' ($newver) in $fileinfo->{ 'dist' } - keeping tmpl v'$oldver'", 5);
2042           return;
2043         }
2044         else {
2045           daemon_log("DEBUG: duplicated '$package' ($newver) in $fileinfo->{ 'dist' } - overwriting tmpl v'$oldver'", 5);
2046         }
2047       }
2048       $templates{ "$fileinfo->{ 'dist' }/$package" } = $newver;
2049       move( "$dir/DEBIAN/templates", "$dir/$package.templates" );
2050     }
2051   }
2055 #==== MAIN = main ==============================================================
2056 #  parse commandline options
2057 Getopt::Long::Configure( "bundling" );
2058 GetOptions("h|help" => \&usage,
2059         "c|config=s" => \$cfg_file,
2060         "f|foreground" => \$foreground,
2061         "v|verbose+" => \$verbose,
2062         "no-bus+" => \$no_bus,
2063         "no-arp+" => \$no_arp,
2064            );
2066 #  read and set config parameters
2067 &check_cmdline_param ;
2068 &read_configfile;
2069 &check_pid;
2071 $SIG{CHLD} = 'IGNORE';
2073 # forward error messages to logfile
2074 if( ! $foreground ) {
2075   open( STDIN,  '+>/dev/null' );
2076   open( STDOUT, '+>&STDIN'    );
2077   open( STDERR, '+>&STDIN'    );
2080 # Just fork, if we are not in foreground mode
2081 if( ! $foreground ) { 
2082     chdir '/'                 or die "Can't chdir to /: $!";
2083     $pid = fork;
2084     setsid                    or die "Can't start a new session: $!";
2085     umask 0;
2086 } else { 
2087     $pid = $$; 
2090 # Do something useful - put our PID into the pid_file
2091 if( 0 != $pid ) {
2092     open( LOCK_FILE, ">$pid_file" );
2093     print LOCK_FILE "$pid\n";
2094     close( LOCK_FILE );
2095     if( !$foreground ) { 
2096         exit( 0 ) 
2097     };
2100 daemon_log(" ", 1);
2101 daemon_log("$0 started!", 1);
2103 if ($no_bus > 0) {
2104     $bus_activ = "false"
2109 # delete old DBsqlite lock files
2110 #unlink('/tmp/gosa_si_lock*');
2112 # connect to gosa-si job queue
2113 $job_db = GOSA::DBsqlite->new($job_queue_file_name);
2114 $job_db->create_table($job_queue_tn, \@job_queue_col_names);
2116 # connect to known_clients_db
2117 $known_clients_db = GOSA::DBsqlite->new($known_clients_file_name);
2118 $known_clients_db->create_table($known_clients_tn, \@known_clients_col_names);
2120 # connect to known_server_db
2121 $known_server_db = GOSA::DBsqlite->new($known_server_file_name);
2122 $known_server_db->create_table($known_server_tn, \@known_server_col_names);
2124 # connect to login_usr_db
2125 $login_users_db = GOSA::DBsqlite->new($login_users_file_name);
2126 $login_users_db->create_table($login_users_tn, \@login_users_col_names);
2128 # connect to fai_server_db and fai_release_db
2129 $fai_server_db = GOSA::DBsqlite->new($fai_server_file_name);
2130 $fai_server_db->create_table($fai_server_tn, \@fai_server_col_names);
2131 $fai_server_db->create_table($fai_release_tn, \@fai_release_col_names);
2133 # connect to packages_list_db
2134 unlink($packages_list_file_name);
2135 $packages_list_db = GOSA::DBsqlite->new($packages_list_file_name);
2136 $packages_list_db->create_table($packages_list_tn, \@packages_list_col_names);
2138 # connect to messaging_db
2139 $messaging_db = GOSA::DBsqlite->new($messaging_file_name);
2140 $messaging_db->create_table($messaging_tn, \@messaging_col_names);
2143 # create xml object used for en/decrypting
2144 $xml = new XML::Simple();
2146 # create socket for incoming xml messages
2148 POE::Component::Server::TCP->new(
2149         Port => $server_port,
2150         ClientInput => sub {
2151         my ($kernel, $input) = @_[KERNEL, ARG0];
2152         push(@tasks, $input);
2153         $kernel->yield("next_task");
2154         },
2155     InlineStates => {
2156         next_task => \&next_task,
2157         task_result => \&handle_task_result,
2158         task_done   => \&handle_task_done,
2159         task_debug  => \&handle_task_debug,
2160         child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!"  },
2161     }
2162 );
2164 daemon_log("start socket for incoming xml messages at port '$server_port' ", 1);
2166 # create session for repeatedly checking the job queue for jobs
2167 POE::Session->create(
2168         inline_states => {
2169                 _start => \&_start,
2170                 sig_handler => \&sig_handler,
2171                 watch_for_new_jobs => \&watch_for_new_jobs,
2172         watch_for_done_jobs => \&watch_for_done_jobs,
2173         create_packages_list_db => \&run_create_packages_list_db,
2174         create_fai_server_db => \&run_create_fai_server_db,
2175         create_fai_release_db => \&run_create_fai_release_db,
2176         session_run_result => \&session_run_result,
2177         session_run_debug => \&session_run_debug,
2178         session_run_done => \&session_run_done,
2179         child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!"  },
2180         }
2181 );
2184 # import all modules
2185 &import_modules;
2187 # check wether all modules are gosa-si valid passwd check
2189 POE::Kernel->run();
2190 exit;