Code

Updated si-server
[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         install   => 'install',
1338     );
1340     # Return if this is unknown
1341     if (!exists $mapActions{ $st }){
1342         daemon_log("$session_id ERROR: unknown action '$st', can not translate ot FAIstate", 1); 
1343       return;
1344     }
1346     my $state= $mapActions{ $st };
1348     &refresh_ldap_handle();
1349     if( defined($ldap_handle) ) {
1351       # Build search filter for hosts
1352         my $search= "(&(objectClass=GOhard)";
1353         foreach (@{$targets}){
1354             $search.= "(macAddress=$_)";
1355         }
1356         $search.= ")";
1358       # If there's any host inside of the search string, procress them
1359         if (!($search =~ /macAddress/)){
1360             daemon_log("$session_id ERROR: no macAddress found in filter statement for LDAP search: '$search'", 1);    
1361             return;
1362         }
1364       # Perform search for Unit Tag
1365       my $mesg = $ldap_handle->search(
1366           base   => $ldap_base,
1367           scope  => 'sub',
1368           attrs  => ['dn', 'FAIstate', 'objectClass'],
1369           filter => "$search"
1370           );
1372       if ($mesg->count) {
1373         my @entries = $mesg->entries;
1374         foreach my $entry (@entries) {
1375           # Only modify entry if it is not set to '$state'
1376           if ($entry->get_value("FAIstate") ne "$state"){
1377             daemon_log("$session_id INFO: Setting FAIstate to '$state' for ".$entry->dn, 5);
1378             my $result;
1379             my %tmp = map { $_ => 1 } $entry->get_value("objectClass");
1380             if (exists $tmp{'FAIobject'}){
1381               if ($state eq ''){
1382                 $result= $ldap_handle->modify($entry->dn, changes => [
1383                             delete => [ FAIstate => [] ] ]);
1384               } else {
1385                 $result= $ldap_handle->modify($entry->dn, changes => [
1386                             replace => [ FAIstate => $state ] ]);
1387               }
1388             } elsif ($state ne ''){
1389               $result= $ldap_handle->modify($entry->dn, changes => [
1390                           add     => [ objectClass => 'FAIobject' ],
1391                           add     => [ FAIstate => $state ] ]);
1392             }
1394             # Errors?
1395             if ($result->code){
1396               daemon_log("$session_id Error: Setting FAIstate to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
1397             }
1399           } else {
1400             daemon_log("$session_id DEBUG FAIstate at host found with filter statement '$search' already at state '$st'", 7); 
1401           }  
1402         }
1403       }
1404     # if no ldap handle defined
1405     } else {
1406         daemon_log("$session_id ERROR: no LDAP handle defined for update FAIstate", 1); 
1407     }
1411 sub change_goto_state {
1412     my ($st, $targets, $session_id) = @_;
1413     $session_id = 0  if not defined $session_id;
1415     # Switch on or off?
1416     my $state= $st eq 'active' ? 'active': 'locked';
1418     &refresh_ldap_handle();
1419     if( defined($ldap_handle) ) {
1421       # Build search filter for hosts
1422       my $search= "(&(objectClass=GOhard)";
1423       foreach (@{$targets}){
1424         $search.= "(macAddress=$_)";
1425       }
1426       $search.= ")";
1428       # If there's any host inside of the search string, procress them
1429       if (!($search =~ /macAddress/)){
1430         return;
1431       }
1433       # Perform search for Unit Tag
1434       my $mesg = $ldap_handle->search(
1435           base   => $ldap_base,
1436           scope  => 'sub',
1437           attrs  => ['dn', 'gotoMode'],
1438           filter => "$search"
1439           );
1441       if ($mesg->count) {
1442         my @entries = $mesg->entries;
1443         foreach my $entry (@entries) {
1445           # Only modify entry if it is not set to '$state'
1446           if ($entry->get_value("gotoMode") ne $state){
1448             daemon_log("$session_id INFO: Setting gotoMode to '$state' for ".$entry->dn, 5);
1449             my $result;
1450             $result= $ldap_handle->modify($entry->dn, changes => [
1451                                                 replace => [ gotoMode => $state ] ]);
1453             # Errors?
1454             if ($result->code){
1455               &daemon_log("$session_id Error: Setting gotoMode to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
1456             }
1458           }
1459         }
1460       }
1462     }
1466 sub create_fai_server_db {
1467     my ($table_name, $kernel) = @_;
1468         my $result;
1470         if(defined($ldap_handle)) {
1471                 daemon_log("INFO: create_fai_server_db: start", 5);
1472                 my $mesg= $ldap_handle->search(
1473                         base   => $ldap_base,
1474                         scope  => 'sub',
1475                         attrs  => ['FAIrepository', 'gosaUnitTag'],
1476                         filter => "(&(FAIrepository=*)(objectClass=FAIrepositoryServer))",
1477                 );
1478                 if($mesg->{'resultCode'} == 0 &&
1479                    $mesg->count != 0) {
1480                    foreach my $entry (@{$mesg->{entries}}) {
1481                            if($entry->exists('FAIrepository')) {
1482                                    # Add an entry for each Repository configured for server
1483                                    foreach my $repo(@{$entry->get_value('FAIrepository', asref => 1)}) {
1484                                                    my($tmp_url,$tmp_server,$tmp_release,$tmp_sections) = split(/\|/, $repo);
1485                                                    my $tmp_tag= $entry->get_value('gosaUnitTag') || "";
1486                                                    $result= $fai_server_db->add_dbentry( { 
1487                                                                    table => $table_name,
1488                                                                    primkey => ['server', 'release', 'tag'],
1489                                                                    server => $tmp_url,
1490                                                                    release => $tmp_release,
1491                                                                    sections => $tmp_sections,
1492                                                                    tag => (length($tmp_tag)>0)?$tmp_tag:"",
1493                                                            } );
1494                                            }
1495                                    }
1496                            }
1497                    }
1498                 daemon_log("INFO: create_fai_server_db: finished", 5);
1500                 # TODO: Find a way to post the 'create_packages_list_db' event
1501                 &create_packages_list_db();
1502         }       
1504         return $result;
1507 sub run_create_fai_server_db {
1508     my ($kernel, $session, $heap, $table_name) = @_[KERNEL, SESSION, HEAP, ARG0];
1509     my $task = POE::Wheel::Run->new(
1510             Program => sub { &create_fai_server_db($table_name,$kernel) },
1511             StdoutEvent  => "session_run_result",
1512             StderrEvent  => "session_run_debug",
1513             CloseEvent   => "session_run_done",
1514             );
1516     $heap->{task}->{ $task->ID } = $task;
1517     return;
1521 sub create_fai_release_db {
1522         my ($table_name) = @_;
1523         my $result;
1525         if(defined($ldap_handle)) {
1526                 daemon_log("INFO: create_fai_release_db: start",5);
1527                 my $mesg= $ldap_handle->search(
1528                         base   => $ldap_base,
1529                         scope  => 'sub',
1530                         attrs  => [],
1531                         filter => "(&(objectClass=organizationalUnit)(ou=fai))",
1532                 );
1533                 if($mesg->{'resultCode'} == 0 &&
1534                         $mesg->count != 0) {
1535                         # Walk through all possible FAI container ou's
1536                         my @sql_list;
1537                         my $timestamp= &get_time();
1538                         foreach my $ou (@{$mesg->{entries}}) {
1539                                 my $tmp_classes= resolve_fai_classes($ou->dn);
1540                                 if(defined($tmp_classes) && ref($tmp_classes) eq 'HASH') {
1541                                         my @tmp_array=get_fai_release_entries($tmp_classes);
1542                                         if(@tmp_array) {
1543                                                 foreach my $entry (@tmp_array) {
1544                                                         if(defined($entry) && ref($entry) eq 'HASH') {
1545                                                                 my $sql= 
1546                                                                 "INSERT INTO $table_name "
1547                                                                 ."(timestamp, release, class, type, state) VALUES ("
1548                                                                 .$timestamp.","
1549                                                                 ."'".$entry->{'release'}."',"
1550                                                                 ."'".$entry->{'class'}."',"
1551                                                                 ."'".$entry->{'type'}."',"
1552                                                                 ."'".$entry->{'state'}."')";
1553                                                                 push @sql_list, $sql;
1554                                                         }
1555                                                 }
1556                                         }
1557                                 }
1558                         }
1559                         daemon_log("DEBUG: Inserting ".scalar @sql_list." entries to DB",6);
1560                         if(@sql_list) {
1561                                 unshift @sql_list, "DELETE FROM $table_name";
1562                                 $fai_server_db->exec_statementlist(\@sql_list);
1563                         }
1564                         daemon_log("DEBUG: Done with inserting",6);
1565                 }
1566                 daemon_log("INFO: create_fai_release_db: finished",5);
1567         }
1569         return $result;
1571 sub run_create_fai_release_db {
1572     my ($session, $heap, $table_name) = @_[SESSION, HEAP, ARG0];
1573     my $task = POE::Wheel::Run->new(
1574             Program => sub { &create_fai_release_db($table_name) },
1575             StdoutEvent  => "session_run_result",
1576             StderrEvent  => "session_run_debug",
1577             CloseEvent   => "session_run_done",
1578             );
1580     $heap->{task}->{ $task->ID } = $task;
1581     return;
1584 sub get_fai_types {
1585         my $tmp_classes = shift || return undef;
1586         my @result;
1588         foreach my $type(keys %{$tmp_classes}) {
1589                 if(defined($tmp_classes->{$type}[0]) && (!($tmp_classes->{$type}[0] =~ /^.*?removed.*?$/))) {
1590                         my $entry = {
1591                                 type => $type,
1592                                 state => $tmp_classes->{$type}[0],
1593                         };
1594                         push @result, $entry;
1595                 }
1596         }
1598         return @result;
1601 sub get_fai_state {
1602         my $result = "";
1603         my $tmp_classes = shift || return $result;
1605         foreach my $type(keys %{$tmp_classes}) {
1606                 if(defined($tmp_classes->{$type}[0])) {
1607                         $result = $tmp_classes->{$type}[0];
1608                         
1609                 # State is equal for all types in class
1610                         last;
1611                 }
1612         }
1614         return $result;
1617 sub resolve_fai_classes {
1618         my $result;
1619         my $fai_base= shift;
1620         my @possible_fai_classes= ("FAIscript", "FAIhook", "FAIpartitionTable", "FAItemplate", "FAIvariable", "FAIprofile", "FAIpackageList");
1621         my $fai_filter= "(|(&(objectClass=FAIclass)(|(objectClass=".join(")(objectClass=", @possible_fai_classes).")))(objectClass=FAIbranch))";
1622         my $fai_classes;
1624         daemon_log("DEBUG: Searching for FAI entries in base $fai_base",6);
1625         my $mesg= $ldap_handle->search(
1626                 base   => $fai_base,
1627                 scope  => 'sub',
1628                 attrs  => ['cn','objectClass','FAIstate'],
1629                 filter => $fai_filter,
1630         );
1631         daemon_log("DEBUG: Found ".$mesg->count()." FAI entries",6);
1633         if($mesg->{'resultCode'} == 0 &&
1634                 $mesg->count != 0) {
1635                 foreach my $entry (@{$mesg->{entries}}) {
1636                         if($entry->exists('cn')) {
1637                                 my $tmp_dn= $entry->dn();
1639                                 # Skip classname and ou dn parts for class
1640                                 my $tmp_release = ($1) if $tmp_dn =~ /^[^,]+,[^,]+,(.*?),$fai_base$/;
1642                                 # Skip classes without releases
1643                                 if((!defined($tmp_release)) || length($tmp_release)==0) {
1644                                         next;
1645                                 }
1647                                 my $tmp_cn= $entry->get_value('cn');
1648                                 my $tmp_state= $entry->get_value('FAIstate');
1650                                 my $tmp_type;
1651                                 # Get FAI type
1652                                 for my $oclass(@{$entry->get_value('objectClass', asref => 1)}) {
1653                                         if(grep $_ eq $oclass, @possible_fai_classes) {
1654                                                 $tmp_type= $oclass;
1655                                                 last;
1656                                         }
1657                                 }
1659                                 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
1660                                         # A Subrelease
1661                                         my @sub_releases = split(/,/, $tmp_release);
1663                                         # Walk through subreleases and build hash tree
1664                                         my $hash;
1665                                         while(my $tmp_sub_release = pop @sub_releases) {
1666                                                 $hash .= "\{'$tmp_sub_release'\}->";                                            
1667                                         }
1668                                         eval('push @{$fai_classes->'.$hash.'{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";');
1669                                 } else {
1670                                         # A branch, no subrelease
1671                                         push @{$fai_classes->{$tmp_release}->{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";
1672                                 }
1673                         } elsif (!$entry->exists('cn')) {
1674                                 my $tmp_dn= $entry->dn();
1675                                 my $tmp_release = ($1) if $tmp_dn =~ /^(.*?),$fai_base$/;
1677                                 # Skip classes without releases
1678                                 if((!defined($tmp_release)) || length($tmp_release)==0) {
1679                                         next;
1680                                 }
1682                                 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
1683                                         # A Subrelease
1684                                         my @sub_releases= split(/,/, $tmp_release);
1686                                         # Walk through subreleases and build hash tree
1687                                         my $hash;
1688                                         while(my $tmp_sub_release = pop @sub_releases) {
1689                                                 $hash .= "\{'$tmp_sub_release'\}->";                                            
1690                                         }
1691                                         # Remove the last two characters
1692                                         chop($hash);
1693                                         chop($hash);
1695                                         eval('$fai_classes->'.$hash.'= {}');
1696                                 } else {
1697                                         # A branch, no subrelease
1698                                         if(!exists($fai_classes->{$tmp_release})) {
1699                                                 $fai_classes->{$tmp_release} = {};
1700                                         }
1701                                 }
1702                         }
1703                 }
1705                 # The hash is complete, now we can honor the copy-on-write based missing entries
1706                 foreach my $release (keys %$fai_classes) {
1707                         $result->{$release}= deep_copy(apply_fai_inheritance($fai_classes->{$release}));
1708                 }
1709         }
1710         return $result;
1713 sub apply_fai_inheritance {
1714        my $fai_classes = shift || return {};
1715        my $tmp_classes;
1717        # Get the classes from the branch
1718        foreach my $class (keys %{$fai_classes}) {
1719                # Skip subreleases
1720                if($class =~ /^ou=.*$/) {
1721                        next;
1722                } else {
1723                        $tmp_classes->{$class}= deep_copy($fai_classes->{$class});
1724                }
1725        }
1727        # Apply to each subrelease
1728        foreach my $subrelease (keys %{$fai_classes}) {
1729                if($subrelease =~ /ou=/) {
1730                        foreach my $tmp_class (keys %{$tmp_classes}) {
1731                                if(!exists($fai_classes->{$subrelease}->{$tmp_class})) {
1732                                        $fai_classes->{$subrelease}->{$tmp_class} =
1733                                        deep_copy($tmp_classes->{$tmp_class});
1734                                } else {
1735                                        foreach my $type (keys %{$tmp_classes->{$tmp_class}}) {
1736                                                if(!exists($fai_classes->{$subrelease}->{$tmp_class}->{$type})) {
1737                                                        $fai_classes->{$subrelease}->{$tmp_class}->{$type}=
1738                                                        deep_copy($tmp_classes->{$tmp_class}->{$type});
1739                                                }
1740                                        }
1741                                }
1742                        }
1743                }
1744        }
1746        # Find subreleases in deeper levels
1747        foreach my $subrelease (keys %{$fai_classes}) {
1748                if($subrelease =~ /ou=/) {
1749                        foreach my $subsubrelease (keys %{$fai_classes->{$subrelease}}) {
1750                                if($subsubrelease =~ /ou=/) {
1751                                        apply_fai_inheritance($fai_classes->{$subrelease});
1752                                }
1753                        }
1754                }
1755        }
1757        return $fai_classes;
1760 sub get_fai_release_entries {
1761         my $tmp_classes = shift || return;
1762         my $parent = shift || "";
1763         my @result = shift || ();
1765         foreach my $entry (keys %{$tmp_classes}) {
1766                 if(defined($entry)) {
1767                         if($entry =~ /^ou=.*$/) {
1768                                 my $release_name = $entry;
1769                                 $release_name =~ s/ou=//g;
1770                                 if(length($parent)>0) {
1771                                         $release_name = $parent."/".$release_name;
1772                                 }
1773                                 my @bufentries = get_fai_release_entries($tmp_classes->{$entry}, $release_name, @result);
1774                                 foreach my $bufentry(@bufentries) {
1775                                         push @result, $bufentry;
1776                                 }
1777                         } else {
1778                                 my @types = get_fai_types($tmp_classes->{$entry});
1779                                 foreach my $type (@types) {
1780                                         push @result, 
1781                                         {
1782                                                 'class' => $entry,
1783                                                 'type' => $type->{'type'},
1784                                                 'release' => $parent,
1785                                                 'state' => $type->{'state'},
1786                                         };
1787                                 }
1788                         }
1789                 }
1790         }
1792         return @result;
1795 sub deep_copy {
1796         my $this = shift;
1797         if (not ref $this) {
1798                 $this;
1799         } elsif (ref $this eq "ARRAY") {
1800                 [map deep_copy($_), @$this];
1801         } elsif (ref $this eq "HASH") {
1802                 +{map { $_ => deep_copy($this->{$_}) } keys %$this};
1803         } else { die "what type is $_?" }
1807 sub session_run_result {
1808     my ($kernel, $heap, $client_answer) = @_[KERNEL, HEAP, ARG0];    
1809     $kernel->sig(CHLD => "child_reap");
1812 sub session_run_debug {
1813     my $result = $_[ARG0];
1814     print STDERR "$result\n";
1817 sub session_run_done {
1818     my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
1819     delete $heap->{task}->{$task_id};
1822 sub create_sources_list {
1823         my $result="/tmp/gosa_si_tmp_sources_list";
1825         # Remove old file
1826         if(stat($result)) {
1827                 unlink($result);
1828         }
1830         my $fh;
1831         open($fh, ">$result") or return undef;
1832         if(defined($ldap_server_dn) and length($ldap_server_dn) > 0) {
1833                 my $mesg=$ldap_handle->search(
1834                                 base    => $ldap_server_dn,
1835                                 scope   => 'base',
1836                                 attrs   => 'FAIrepository',
1837                                 filter  => 'objectClass=FAIrepositoryServer'
1838                                 );
1839                 if($mesg->count) {
1840                         foreach my $entry(@{$mesg->{'entries'}}) {
1841                                 my ($server, $tag, $release, $sections)= split /\|/, $entry->get_value('FAIrepository');
1842                                 my $line = "deb $server $release";
1843                                 $sections =~ s/,/ /g;
1844                                 $line.= " $sections";
1845                                 print $fh $line."\n";
1846                         }
1847                 }
1848         }
1849         close($fh);
1851         return $result;
1854 sub create_packages_list_db {
1855     my ($sources_file) = @_ || &create_sources_list;
1856     my $line;
1857     daemon_log("INFO: create_packages_list_db: start", 5); 
1859     open(CONFIG, "<$sources_file") or do {
1860         daemon_log( "ERROR: create_packages_list_db: Failed to open '$sources_file'", 1);
1861         return;
1862     };
1863     
1864     # Read lines
1865     while ($line = <CONFIG>){
1866         # Unify
1867         chop($line);
1868         $line =~ s/^\s+//;
1869         $line =~ s/^\s+/ /;
1871         # Strip comments
1872         $line =~ s/#.*$//g;
1874         # Skip empty lines
1875         if ($line =~ /^\s*$/){
1876             next;
1877         }
1879         # Interpret deb line
1880         if ($line =~ /^deb [^\s]+\s[^\s]+\s[^\s]+/){
1881             my( $baseurl, $dist, $sections ) = ($line =~ /^deb\s([^\s]+)\s+([^\s]+)\s+(.*)$/);
1882             my $section;
1883             foreach $section (split(' ', $sections)){
1884                 &parse_package_info( $baseurl, $dist, $section );
1885             }
1886         }
1887     }
1889     close (CONFIG);
1891     daemon_log("INFO: create_packages_list_db: finished", 5); 
1892     return;
1895 sub run_create_packages_list_db {
1896     my ($session, $heap) = @_[SESSION, HEAP];
1897     my $task = POE::Wheel::Run->new(
1898             Program => sub {&create_packages_list_db},
1899             StdoutEvent  => "session_run_result",
1900             StderrEvent  => "session_run_debug",
1901             CloseEvent   => "session_run_done",
1902             );
1903     $heap->{task}->{ $task->ID } = $task;
1906 sub parse_package_info {
1907   my ($baseurl, $dist, $section)= @_;
1908   my ($package);
1910   my ($path) = ($baseurl =~ m%://[^/]*(.*)$%);
1911   $repo_dirs{ "${repo_path}/pool" } = 1;
1913   foreach $package ("Packages.gz"){
1914     daemon_log("DEBUG: create_packages_list: fetch $baseurl, $dist, $section", 7);
1915     get_package( "$baseurl/dists/$dist/$section/binary-$arch/$package", "$outdir/$dist/$section" );
1916     parse_package( "$outdir/$dist/$section", $dist, $path );
1917   }
1918   find(\&cleanup_and_extract, keys( %repo_dirs ) );
1921 sub get_package {
1922   my ($url, $dest)= @_;
1924   my $tpath = dirname($dest);
1925   -d "$tpath" || mkpath "$tpath";
1927   # This is ugly, but I've no time to take a look at "how it works in perl"
1928   if(0 == system("wget '$url' -O '$dest' 2>/dev/null") ) {
1929       system("gunzip -cd '$dest' > '$dest.in'");
1930       unlink($dest);
1931   } else {
1932       daemon_log("ERROR: create_packages_list_db: get_packages: fetching '$url' failed!", 1);
1933   }
1934   return 0;
1937 sub parse_package {
1938     my ($path, $dist, $srv_path)= @_;
1939     my ($package, $version, $section, $description);
1940     my @sql_list;
1941     my $PACKAGES;
1943     if(not stat("$path.in")) {
1944         daemon_log("ERROR: create_packages_list: parse_package: file '$path.in' is not readable",1);
1945         return;
1946     }
1948     open($PACKAGES, "<$path.in");
1949         if(not defined($PACKAGES)) {
1950         daemon_log("ERROR: create_packages_list_db: parse_package: can not open '$path.in'",1); 
1951         return;
1952     }
1954     # Read lines
1955     while (<$PACKAGES>){
1956         my $line = $_;
1957         # Unify
1958         chop($line);
1960         # Use empty lines as a trigger
1961         if ($line =~ /^\s*$/){
1962             my $sql = "INSERT INTO packages_list VALUES ('$dist', '$package', '$version', '$section', '', 'none', '0')";
1963             push(@sql_list, $sql);
1964             $package = "none";
1965             $version = "none";
1966             $section = "none";
1967             $description = "none"; 
1968             next;
1969         }
1971         # Trigger for package name
1972         if ($line =~ /^Package:\s/){
1973             ($package)= ($line =~ /^Package: (.*)$/);
1974             next;
1975         }
1977         # Trigger for version
1978         if ($line =~ /^Version:\s/){
1979             ($version)= ($line =~ /^Version: (.*)$/);
1980             next;
1981         }
1983         # Trigger for description
1984         if ($line =~ /^Description:\s/){
1985             ($description)= ($line =~ /^Description: (.*)$/);
1986             next;
1987         }
1989         # Trigger for section
1990         if ($line =~ /^Section:\s/){
1991             ($section)= ($line =~ /^Section: (.*)$/);
1992             next;
1993         }
1995         # Trigger for filename
1996         if ($line =~ /^Filename:\s/){
1997                 my ($filename) = ($line =~ /^Filename: (.*)$/);
1998                 store_fileinfo( $package, $filename, $dist, $srv_path, $version, $repo_path );
1999                 next;
2000         }
2001     }
2003     close( $PACKAGES );
2004     unlink( "$path.in" );
2005     
2006     $packages_list_db->exec_statementlist(\@sql_list);
2009 sub store_fileinfo {
2010   my( $package, $file, $dist, $path, $vers, $srvdir) = @_;
2012   my %fileinfo = (
2013     'package' => $package,
2014     'dist' => $dist,
2015     'version' => $vers,
2016   );
2018   $repo_files{ "${srvdir}/$file" } = \%fileinfo;
2021 sub cleanup_and_extract {
2022   my $fileinfo = $repo_files{ $File::Find::name };
2024   if( defined $fileinfo ) {
2026     my $dir = "$outdir/$fileinfo->{ 'dist' }/debconf.d";
2027     my $sql;
2028     my $package = $fileinfo->{ 'package' };
2029     my $newver = $fileinfo->{ 'version' };
2031     mkpath($dir);
2032     system( "dpkg -e '$File::Find::name' '$dir/DEBIAN'" );
2034     if( -f "$dir/DEBIAN/templates" ) {
2036       daemon_log("DEBUG: Found debconf templates in '$package' - $newver", 5);
2038       my $tmpl= "";
2039       {
2040           local $/=undef;
2041           open FILE, "$dir/DEBIAN/templates";
2042           $tmpl = &encode_base64(<FILE>);
2043           close FILE;
2044       }
2045       rmtree("$dir/DEBIAN/templates");
2047       $sql= "update $main::packages_list_tn set template = '$tmpl' where package = '$package' and version = '$newver';";
2049     } else {
2050       $sql= "update $main::packages_list_tn set template = '' where package = '$package' and version = '$newver';";
2051     }
2053     my $res= $main::packages_list_db->update_dbentry($sql);
2054   }
2058 #==== MAIN = main ==============================================================
2059 #  parse commandline options
2060 Getopt::Long::Configure( "bundling" );
2061 GetOptions("h|help" => \&usage,
2062         "c|config=s" => \$cfg_file,
2063         "f|foreground" => \$foreground,
2064         "v|verbose+" => \$verbose,
2065         "no-bus+" => \$no_bus,
2066         "no-arp+" => \$no_arp,
2067            );
2069 #  read and set config parameters
2070 &check_cmdline_param ;
2071 &read_configfile;
2072 &check_pid;
2074 $SIG{CHLD} = 'IGNORE';
2076 # forward error messages to logfile
2077 if( ! $foreground ) {
2078   open( STDIN,  '+>/dev/null' );
2079   open( STDOUT, '+>&STDIN'    );
2080   open( STDERR, '+>&STDIN'    );
2083 # Just fork, if we are not in foreground mode
2084 if( ! $foreground ) { 
2085     chdir '/'                 or die "Can't chdir to /: $!";
2086     $pid = fork;
2087     setsid                    or die "Can't start a new session: $!";
2088     umask 0;
2089 } else { 
2090     $pid = $$; 
2093 # Do something useful - put our PID into the pid_file
2094 if( 0 != $pid ) {
2095     open( LOCK_FILE, ">$pid_file" );
2096     print LOCK_FILE "$pid\n";
2097     close( LOCK_FILE );
2098     if( !$foreground ) { 
2099         exit( 0 ) 
2100     };
2103 daemon_log(" ", 1);
2104 daemon_log("$0 started!", 1);
2106 if ($no_bus > 0) {
2107     $bus_activ = "false"
2112 # delete old DBsqlite lock files
2113 #unlink('/tmp/gosa_si_lock*');
2115 # connect to gosa-si job queue
2116 $job_db = GOSA::DBsqlite->new($job_queue_file_name);
2117 $job_db->create_table($job_queue_tn, \@job_queue_col_names);
2119 # connect to known_clients_db
2120 $known_clients_db = GOSA::DBsqlite->new($known_clients_file_name);
2121 $known_clients_db->create_table($known_clients_tn, \@known_clients_col_names);
2123 # connect to known_server_db
2124 $known_server_db = GOSA::DBsqlite->new($known_server_file_name);
2125 $known_server_db->create_table($known_server_tn, \@known_server_col_names);
2127 # connect to login_usr_db
2128 $login_users_db = GOSA::DBsqlite->new($login_users_file_name);
2129 $login_users_db->create_table($login_users_tn, \@login_users_col_names);
2131 # connect to fai_server_db and fai_release_db
2132 $fai_server_db = GOSA::DBsqlite->new($fai_server_file_name);
2133 $fai_server_db->create_table($fai_server_tn, \@fai_server_col_names);
2134 $fai_server_db->create_table($fai_release_tn, \@fai_release_col_names);
2136 # connect to packages_list_db
2137 unlink($packages_list_file_name);
2138 $packages_list_db = GOSA::DBsqlite->new($packages_list_file_name);
2139 $packages_list_db->create_table($packages_list_tn, \@packages_list_col_names);
2141 # connect to messaging_db
2142 $messaging_db = GOSA::DBsqlite->new($messaging_file_name);
2143 $messaging_db->create_table($messaging_tn, \@messaging_col_names);
2146 # create xml object used for en/decrypting
2147 $xml = new XML::Simple();
2149 # create socket for incoming xml messages
2151 POE::Component::Server::TCP->new(
2152         Port => $server_port,
2153         ClientInput => sub {
2154         my ($kernel, $input) = @_[KERNEL, ARG0];
2155         push(@tasks, $input);
2156         $kernel->yield("next_task");
2157         },
2158     InlineStates => {
2159         next_task => \&next_task,
2160         task_result => \&handle_task_result,
2161         task_done   => \&handle_task_done,
2162         task_debug  => \&handle_task_debug,
2163         child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!"  },
2164     }
2165 );
2167 daemon_log("start socket for incoming xml messages at port '$server_port' ", 1);
2169 # create session for repeatedly checking the job queue for jobs
2170 POE::Session->create(
2171         inline_states => {
2172                 _start => \&_start,
2173                 sig_handler => \&sig_handler,
2174                 watch_for_new_jobs => \&watch_for_new_jobs,
2175         watch_for_done_jobs => \&watch_for_done_jobs,
2176         create_packages_list_db => \&run_create_packages_list_db,
2177         create_fai_server_db => \&run_create_fai_server_db,
2178         create_fai_release_db => \&run_create_fai_release_db,
2179         session_run_result => \&session_run_result,
2180         session_run_debug => \&session_run_debug,
2181         session_run_done => \&session_run_done,
2182         child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!"  },
2183         }
2184 );
2187 # import all modules
2188 &import_modules;
2190 # check wether all modules are gosa-si valid passwd check
2192 POE::Kernel->run();
2193 exit;