Code

0b106ae047c2659bea20d8ab8e0dc97e43a7ad8c
[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].
1063                                 "' from '".$heap->{'remote_ip'}."'", 5); 
1064         daemon_log("$session_id DEBUG: Processing module ".$module, 7);
1065         $answer_l = &{ $module."::process_incoming_msg" }($msg, $msg_hash, $session_id);
1067         if ( 0 < @{$answer_l} ) {
1068             my $answer_str = join("\n", @{$answer_l});
1069             daemon_log("$session_id DEBUG: $module: Got answer from module: \n".$answer_str,8);
1070         }
1071     }
1072     if( !$answer_l ) { $error++ };
1074     ########
1075     # answer
1076     if( $error == 0 ) {
1078         foreach my $answer ( @{$answer_l} ) {
1079             # for each answer in answer list
1080             
1081             # check outgoing msg to xml validity
1082             my $answer_hash = &check_outgoing_xml_validity($answer);
1083             if( not defined $answer_hash ) {
1084                 next;
1085             }
1086             
1087             $answer_header = @{$answer_hash->{'header'}}[0];
1088             @answer_target_l = @{$answer_hash->{'target'}};
1089             $answer_source = @{$answer_hash->{'source'}}[0];
1091             # deliver msg to all targets 
1092             foreach my $answer_target ( @answer_target_l ) {
1094                 # targets of msg are all gosa-si-clients in known_clients_db
1095                 if( $answer_target eq "*" ) {
1096                     # answer is for all clients
1097                     my $sql_statement= "SELECT * FROM known_clients";
1098                     my $query_res = $known_clients_db->select_dbentry( $sql_statement ); 
1099                     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1100                         my $host_name = $hit->{hostname};
1101                         my $host_key = $hit->{hostkey};
1102                         my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1103                         &update_jobdb_status_for_send_msgs($answer, $error);
1104                     }
1105                 }
1107                 # targets of msg are all gosa-si-server in known_server_db
1108                 elsif( $answer_target eq "KNOWN_SERVER" ) {
1109                     # answer is for all server in known_server
1110                     my $sql_statement= "SELECT * FROM known_server";
1111                     my $query_res = $known_server_db->select_dbentry( $sql_statement ); 
1112                     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1113                         my $host_name = $hit->{hostname};
1114                         my $host_key = $hit->{hostkey};
1115                         $answer =~ s/KNOWN_SERVER/$host_name/g;
1116                         my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1117                         &update_jobdb_status_for_send_msgs($answer, $error);
1118                     }
1119                 }
1121                 # target of msg is GOsa
1122                                 elsif( $answer_target eq "GOSA" ) {
1123                                         my $session_id = ($1) if $answer =~ /<session_id>(\d+?)<\/session_id>/;
1124                                         my $add_on = "";
1125                     if( defined $session_id ) {
1126                         $add_on = ".session_id=$session_id";
1127                     }
1128                     # answer is for GOSA and has to returned to connected client
1129                     my $gosa_answer = &encrypt_msg($answer, $GosaPackages_key);
1130                     $client_answer = $gosa_answer.$add_on;
1131                 }
1133                 # target of msg is job queue at this host
1134                 elsif( $answer_target eq "JOBDB") {
1135                     $answer =~ /<header>(\S+)<\/header>/;   
1136                     my $header;
1137                     if( defined $1 ) { $header = $1; }
1138                     my $error = &send_msg_to_target($answer, $server_address, $GosaPackages_key, $header, $session_id);
1139                     &update_jobdb_status_for_send_msgs($answer, $error);
1140                 }
1142                 # target of msg is a mac address
1143                 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 ) {
1144                     daemon_log("$session_id INFO: target is mac address '$answer_target', looking for host in known_clients", 5);
1145                     my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$answer_target'";
1146                     my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1147                     my $found_ip_flag = 0;
1148                     while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1149                         my $host_name = $hit->{hostname};
1150                         my $host_key = $hit->{hostkey};
1151                         $answer =~ s/$answer_target/$host_name/g;
1152                         daemon_log("$session_id INFO: found host '$host_name', associated to '$answer_target'", 5);
1153                         my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1154                         &update_jobdb_status_for_send_msgs($answer, $error);
1155                         $found_ip_flag++ ;
1156                     }   
1157                     if( $found_ip_flag == 0) {
1158                         daemon_log("$session_id WARNING: no host found in known_clients with mac address '$answer_target'", 3);
1159                         if( $bus_activ eq "true" ) { 
1160                             daemon_log("$session_id INFO: try to forward msg '$answer_header' to bus '$bus_address'", 5);
1161                             my $sql_statement = "SELECT * FROM known_server WHERE hostname='$bus_address'";
1162                             my $query_res = $known_server_db->select_dbentry( $sql_statement );
1163                             while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1164                                 my $bus_address = $hit->{hostname};
1165                                 my $bus_key = $hit->{hostkey};
1166                                 my $error = &send_msg_to_target($answer, $bus_address, $bus_key, $answer_header, $session_id);
1167                                 &update_jobdb_status_for_send_msgs($answer, $error);
1168                                 last;
1169                             }
1170                         }
1172                     }
1174                 #  answer is for one specific host   
1175                 } else {
1176                     # get encrypt_key
1177                     my $encrypt_key = &get_encrypt_key($answer_target);
1178                     if( not defined $encrypt_key ) {
1179                         # unknown target, forward msg to bus
1180                         daemon_log("$session_id WARNING: unknown target '$answer_target'", 3);
1181                         if( $bus_activ eq "true" ) { 
1182                             daemon_log("$session_id INFO: try to forward msg '$answer_header' to bus '$bus_address'", 5);
1183                             my $sql_statement = "SELECT * FROM known_server WHERE hostname='$bus_address'";
1184                             my $query_res = $known_server_db->select_dbentry( $sql_statement );
1185                             my $res_length = keys( %{$query_res} );
1186                             if( $res_length == 0 ){
1187                                 daemon_log("$session_id WARNING: send '$answer_header' to '$bus_address' failed, ".
1188                                         "no bus found in known_server", 3);
1189                             }
1190                             else {
1191                                 while( my ($hit_num, $hit) = each %{ $query_res } ) {    
1192                                     my $bus_key = $hit->{hostkey};
1193                                     my $error = &send_msg_to_target($answer, $bus_address, $bus_key, $answer_header,$session_id );
1194                                     &update_jobdb_status_for_send_msgs($answer, $error);
1195                                 }
1196                             }
1197                         }
1198                         next;
1199                     }
1200                     my $error = &send_msg_to_target($answer, $answer_target, $encrypt_key, $answer_header,$session_id);
1201                     &update_jobdb_status_for_send_msgs($answer, $error);
1202                 }
1203             }
1204         }
1205     }
1207     my $filter = POE::Filter::Reference->new();
1208     my %result = ( 
1209             status => "seems ok to me",
1210             answer => $client_answer,
1211             );
1213     my $output = $filter->put( [ \%result ] );
1214     print @$output;
1220 sub trigger_db_loop {
1221         my ($kernel) = @_ ;
1222         $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1223     $kernel->delay_set('watch_for_done_jobs', $job_queue_loop_delay); 
1226 sub watch_for_done_jobs {
1227     my ($kernel,$heap) = @_[KERNEL, HEAP];
1229     my $sql_statement = "SELECT * FROM ".$job_queue_tn.
1230         " WHERE status='done'";
1231         my $res = $job_db->select_dbentry( $sql_statement );
1233     while( my ($id, $hit) = each %{$res} ) {
1234         my $jobdb_id = $hit->{id};
1235         my $sql_statement = "DELETE FROM $job_queue_tn WHERE id='$jobdb_id'"; 
1236         my $res = $job_db->del_dbentry($sql_statement);
1237     }
1239     $kernel->delay_set('watch_for_done_jobs',$job_queue_loop_delay);
1242 sub watch_for_new_jobs {
1243         my ($kernel,$heap) = @_[KERNEL, HEAP];
1245         # check gosa job queue for jobs with executable timestamp
1246     my $timestamp = &get_time();
1247     my $sql_statement = "SELECT * FROM ".$job_queue_tn.
1248         " WHERE status='waiting' AND timestamp<'$timestamp'";
1249         my $res = $job_db->select_dbentry( $sql_statement );
1251         while( my ($id, $hit) = each %{$res} ) {         
1252                 my $jobdb_id = $hit->{id};
1253                 my $macaddress = $hit->{'macaddress'};
1254         my $job_msg = $hit->{'xmlmessage'};
1255         daemon_log("J DEBUG: its time to execute $job_msg", 7); 
1256         my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$macaddress'";
1257                 my $res_hash = $known_clients_db->select_dbentry( $sql_statement );
1258                 # expect macaddress is unique!!!!!!
1259                 my $target = $res_hash->{1}->{hostname};
1261 #        if (not defined $target) {
1262 #                       &daemon_log("ERROR: no host found for mac address: $macaddress", 1);
1263 #                       &daemon_log("$hit->{xmlmessage}", 8);
1264 #            my $sql_statement = "UPDATE $job_queue_tn ".
1265 #                "SET status='error', result='no host found for mac address' ".
1266 #                "WHERE id='$jobdb_id'";
1267 #                       my $res = $job_db->update_dbentry($sql_statement);
1268 #                       next;
1269 #               }
1271                 # change header
1272         $job_msg =~ s/<header>job_/<header>gosa_/;
1274                 # add sqlite_id 
1275         $job_msg =~ s/<\/xml>$/<jobdb_id>$jobdb_id<\/jobdb_id><\/xml>/;
1277         $job_msg =~ /<header>(\S+)<\/header>/;
1278         my $header = $1 ;
1279                 my $func_error = &send_msg_to_target($job_msg, $server_address, $GosaPackages_key, $header, "J");
1281         # update status in job queue to 'processing'
1282         $sql_statement = "UPDATE $job_queue_tn SET status='processing' WHERE id='$jobdb_id'";
1283         my $res = $job_db->update_dbentry($sql_statement);
1284     }
1286         $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1290 sub refresh_ldap_handle {
1291         my ($session_id) = @_ ;
1292         if (not defined $session_id) { $session_id = 0; } 
1293         
1294   my $mesg;
1296   daemon_log("$session_id DEBUG: Trying to create a connection to URI '$ldap_uri'", 7);
1297   # Get an ldap handle, if we don't have one
1298   if( ! defined $ldap_handle ){
1299           $ldap_handle = Net::LDAP->new( $ldap_uri );
1300   }
1301   # Still not defined?
1302   if( ! defined $ldap_handle ) {
1303           daemon_log( "$session_id ERROR: ch $$: Net::LDAP constructor failed: $!\n" );
1304           return 0;
1305   }
1307   # Bind to ldap server - eventually authenticate
1308   if( defined $ldap_admin_dn ) {
1309     if( defined $ldap_admin_password ) {
1310       $mesg = $ldap_handle->bind( $ldap_admin_dn, password => $ldap_admin_password );
1311     } else {
1312       $mesg = $ldap_handle->bind( $ldap_admin_dn );
1313     }
1314   } else {
1315     $mesg = $ldap_handle->bind();
1316   }
1318   if( 0 != $mesg->code ) {
1319     undef( $ldap_handle ) if( 81 == $mesg->code );
1320     daemon_log( "$session_id ERROR: ch $$: LDAP bind: error (". $mesg->code . ') - ' . $mesg->error . "\n", 1);
1321     return 0;
1322   }
1323         daemon_log("$session_id DEBUG: create a new connection to URI '$ldap_uri'", 7);
1324   return 1;
1328 sub change_fai_state {
1329     my ($st, $targets, $session_id) = @_;
1330     $session_id = 0 if not defined $session_id;
1331     # Set FAI state to localboot
1332     my %mapActions= (
1333         reboot    => '',
1334         update    => 'softupdate',
1335         localboot => 'localboot',
1336         reinstall => 'install',
1337         rescan    => '',
1338         wake      => '',
1339         memcheck  => 'memcheck',
1340         sysinfo   => 'sysinfo',
1341         install   => 'install',
1342     );
1344     # Return if this is unknown
1345     if (!exists $mapActions{ $st }){
1346         daemon_log("$session_id ERROR: unknown action '$st', can not translate ot FAIstate", 1); 
1347       return;
1348     }
1350     my $state= $mapActions{ $st };
1352     &refresh_ldap_handle();
1353     if( defined($ldap_handle) ) {
1355       # Build search filter for hosts
1356         my $search= "(&(objectClass=GOhard)";
1357         foreach (@{$targets}){
1358             $search.= "(macAddress=$_)";
1359         }
1360         $search.= ")";
1362       # If there's any host inside of the search string, procress them
1363         if (!($search =~ /macAddress/)){
1364             daemon_log("$session_id ERROR: no macAddress found in filter statement for LDAP search: '$search'", 1);    
1365             return;
1366         }
1368       # Perform search for Unit Tag
1369       my $mesg = $ldap_handle->search(
1370           base   => $ldap_base,
1371           scope  => 'sub',
1372           attrs  => ['dn', 'FAIstate', 'objectClass'],
1373           filter => "$search"
1374           );
1376       if ($mesg->count) {
1377         my @entries = $mesg->entries;
1378         foreach my $entry (@entries) {
1379           # Only modify entry if it is not set to '$state'
1380           if ($entry->get_value("FAIstate") ne "$state"){
1381             daemon_log("$session_id INFO: Setting FAIstate to '$state' for ".$entry->dn, 5);
1382             my $result;
1383             my %tmp = map { $_ => 1 } $entry->get_value("objectClass");
1384             if (exists $tmp{'FAIobject'}){
1385               if ($state eq ''){
1386                 $result= $ldap_handle->modify($entry->dn, changes => [
1387                             delete => [ FAIstate => [] ] ]);
1388               } else {
1389                 $result= $ldap_handle->modify($entry->dn, changes => [
1390                             replace => [ FAIstate => $state ] ]);
1391               }
1392             } elsif ($state ne ''){
1393               $result= $ldap_handle->modify($entry->dn, changes => [
1394                           add     => [ objectClass => 'FAIobject' ],
1395                           add     => [ FAIstate => $state ] ]);
1396             }
1398             # Errors?
1399             if ($result->code){
1400               daemon_log("$session_id Error: Setting FAIstate to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
1401             }
1403           } else {
1404             daemon_log("$session_id DEBUG FAIstate at host '$_' already at state '$st'", 7); 
1405           }  
1406         }
1407       }
1408     # if no ldap handle defined
1409     } else {
1410         daemon_log("$session_id ERROR: no LDAP handle defined for update FAIstate", 1); 
1411     }
1415 sub change_goto_state {
1416     my ($st, $targets, $session_id) = @_;
1417     $session_id = 0  if not defined $session_id;
1419     # Switch on or off?
1420     my $state= $st eq 'active' ? 'active': 'locked';
1422     &refresh_ldap_handle();
1423     if( defined($ldap_handle) ) {
1425       # Build search filter for hosts
1426       my $search= "(&(objectClass=GOhard)";
1427       foreach (@{$targets}){
1428         $search.= "(macAddress=$_)";
1429       }
1430       $search.= ")";
1432       # If there's any host inside of the search string, procress them
1433       if (!($search =~ /macAddress/)){
1434         return;
1435       }
1437       # Perform search for Unit Tag
1438       my $mesg = $ldap_handle->search(
1439           base   => $ldap_base,
1440           scope  => 'sub',
1441           attrs  => ['dn', 'gotoMode'],
1442           filter => "$search"
1443           );
1445       if ($mesg->count) {
1446         my @entries = $mesg->entries;
1447         foreach my $entry (@entries) {
1449           # Only modify entry if it is not set to '$state'
1450           if ($entry->get_value("gotoMode") ne $state){
1452             daemon_log("$session_id INFO: Setting gotoMode to '$state' for ".$entry->dn, 5);
1453             my $result;
1454             $result= $ldap_handle->modify($entry->dn, changes => [
1455                                                 replace => [ gotoMode => $state ] ]);
1457             # Errors?
1458             if ($result->code){
1459               &daemon_log("$session_id Error: Setting gotoMode to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
1460             }
1462           }
1463         }
1464       }
1466     }
1470 sub create_fai_server_db {
1471     my ($table_name, $kernel) = @_;
1472         my $result;
1474         if(defined($ldap_handle)) {
1475                 daemon_log("INFO: create_fai_server_db: start", 5);
1476                 my $mesg= $ldap_handle->search(
1477                         base   => $ldap_base,
1478                         scope  => 'sub',
1479                         attrs  => ['FAIrepository', 'gosaUnitTag'],
1480                         filter => "(&(FAIrepository=*)(objectClass=FAIrepositoryServer))",
1481                 );
1482                 if($mesg->{'resultCode'} == 0 &&
1483                    $mesg->count != 0) {
1484                    foreach my $entry (@{$mesg->{entries}}) {
1485                            if($entry->exists('FAIrepository')) {
1486                                    # Add an entry for each Repository configured for server
1487                                    foreach my $repo(@{$entry->get_value('FAIrepository', asref => 1)}) {
1488                                                    my($tmp_url,$tmp_server,$tmp_release,$tmp_sections) = split(/\|/, $repo);
1489                                                    my $tmp_tag= $entry->get_value('gosaUnitTag') || "";
1490                                                    $result= $fai_server_db->add_dbentry( { 
1491                                                                    table => $table_name,
1492                                                                    primkey => ['server', 'release', 'tag'],
1493                                                                    server => $tmp_url,
1494                                                                    release => $tmp_release,
1495                                                                    sections => $tmp_sections,
1496                                                                    tag => (length($tmp_tag)>0)?$tmp_tag:"",
1497                                                            } );
1498                                            }
1499                                    }
1500                            }
1501                    }
1502                 daemon_log("INFO: create_fai_server_db: finished", 5);
1504                 # TODO: Find a way to post the 'create_packages_list_db' event
1505                 &create_packages_list_db();
1506         }       
1508         return $result;
1511 sub run_create_fai_server_db {
1512     my ($kernel, $session, $heap, $table_name) = @_[KERNEL, SESSION, HEAP, ARG0];
1513     my $task = POE::Wheel::Run->new(
1514             Program => sub { &create_fai_server_db($table_name,$kernel) },
1515             StdoutEvent  => "session_run_result",
1516             StderrEvent  => "session_run_debug",
1517             CloseEvent   => "session_run_done",
1518             );
1520     $heap->{task}->{ $task->ID } = $task;
1521     return;
1525 sub create_fai_release_db {
1526         my ($table_name) = @_;
1527         my $result;
1529         if(defined($ldap_handle)) {
1530                 daemon_log("INFO: create_fai_release_db: start",5);
1531                 my $mesg= $ldap_handle->search(
1532                         base   => $ldap_base,
1533                         scope  => 'sub',
1534                         attrs  => [],
1535                         filter => "(&(objectClass=organizationalUnit)(ou=fai))",
1536                 );
1537                 if($mesg->{'resultCode'} == 0 &&
1538                         $mesg->count != 0) {
1539                         # Walk through all possible FAI container ou's
1540                         my @sql_list;
1541                         my $timestamp= &get_time();
1542                         foreach my $ou (@{$mesg->{entries}}) {
1543                                 my $tmp_classes= resolve_fai_classes($ou->dn);
1544                                 if(defined($tmp_classes) && ref($tmp_classes) eq 'HASH') {
1545                                         my @tmp_array=get_fai_release_entries($tmp_classes);
1546                                         if(@tmp_array) {
1547                                                 foreach my $entry (@tmp_array) {
1548                                                         if(defined($entry) && ref($entry) eq 'HASH') {
1549                                                                 my $sql= 
1550                                                                 "INSERT INTO $table_name "
1551                                                                 ."(timestamp, release, class, type, state) VALUES ("
1552                                                                 .$timestamp.","
1553                                                                 ."'".$entry->{'release'}."',"
1554                                                                 ."'".$entry->{'class'}."',"
1555                                                                 ."'".$entry->{'type'}."',"
1556                                                                 ."'".$entry->{'state'}."')";
1557                                                                 push @sql_list, $sql;
1558                                                         }
1559                                                 }
1560                                         }
1561                                 }
1562                         }
1563                         daemon_log("DEBUG: Inserting ".scalar @sql_list." entries to DB",6);
1564                         if(@sql_list) {
1565                                 unshift @sql_list, "DELETE FROM $table_name";
1566                                 $fai_server_db->exec_statementlist(\@sql_list);
1567                         }
1568                         daemon_log("DEBUG: Done with inserting",6);
1569                 }
1570                 daemon_log("INFO: create_fai_release_db: finished",5);
1571         }
1573         return $result;
1575 sub run_create_fai_release_db {
1576     my ($session, $heap, $table_name) = @_[SESSION, HEAP, ARG0];
1577     my $task = POE::Wheel::Run->new(
1578             Program => sub { &create_fai_release_db($table_name) },
1579             StdoutEvent  => "session_run_result",
1580             StderrEvent  => "session_run_debug",
1581             CloseEvent   => "session_run_done",
1582             );
1584     $heap->{task}->{ $task->ID } = $task;
1585     return;
1588 sub get_fai_types {
1589         my $tmp_classes = shift || return undef;
1590         my @result;
1592         foreach my $type(keys %{$tmp_classes}) {
1593                 if(defined($tmp_classes->{$type}[0]) && (!($tmp_classes->{$type}[0] =~ /^.*?removed.*?$/))) {
1594                         my $entry = {
1595                                 type => $type,
1596                                 state => $tmp_classes->{$type}[0],
1597                         };
1598                         push @result, $entry;
1599                 }
1600         }
1602         return @result;
1605 sub get_fai_state {
1606         my $result = "";
1607         my $tmp_classes = shift || return $result;
1609         foreach my $type(keys %{$tmp_classes}) {
1610                 if(defined($tmp_classes->{$type}[0])) {
1611                         $result = $tmp_classes->{$type}[0];
1612                         
1613                 # State is equal for all types in class
1614                         last;
1615                 }
1616         }
1618         return $result;
1621 sub resolve_fai_classes {
1622         my $result;
1623         my $fai_base= shift;
1624         my @possible_fai_classes= ("FAIscript", "FAIhook", "FAIpartitionTable", "FAItemplate", "FAIvariable", "FAIprofile", "FAIpackageList");
1625         my $fai_filter= "(|(&(objectClass=FAIclass)(|(objectClass=".join(")(objectClass=", @possible_fai_classes).")))(objectClass=FAIbranch))";
1626         my $fai_classes;
1628         daemon_log("DEBUG: Searching for FAI entries in base $fai_base",6);
1629         my $mesg= $ldap_handle->search(
1630                 base   => $fai_base,
1631                 scope  => 'sub',
1632                 attrs  => ['cn','objectClass','FAIstate'],
1633                 filter => $fai_filter,
1634         );
1635         daemon_log("DEBUG: Found ".$mesg->count()." FAI entries",6);
1637         if($mesg->{'resultCode'} == 0 &&
1638                 $mesg->count != 0) {
1639                 foreach my $entry (@{$mesg->{entries}}) {
1640                         if($entry->exists('cn')) {
1641                                 my $tmp_dn= $entry->dn();
1643                                 # Skip classname and ou dn parts for class
1644                                 my $tmp_release = ($1) if $tmp_dn =~ /^[^,]+,[^,]+,(.*?),$fai_base$/;
1646                                 # Skip classes without releases
1647                                 if((!defined($tmp_release)) || length($tmp_release)==0) {
1648                                         next;
1649                                 }
1651                                 my $tmp_cn= $entry->get_value('cn');
1652                                 my $tmp_state= $entry->get_value('FAIstate');
1654                                 my $tmp_type;
1655                                 # Get FAI type
1656                                 for my $oclass(@{$entry->get_value('objectClass', asref => 1)}) {
1657                                         if(grep $_ eq $oclass, @possible_fai_classes) {
1658                                                 $tmp_type= $oclass;
1659                                                 last;
1660                                         }
1661                                 }
1663                                 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
1664                                         # A Subrelease
1665                                         my @sub_releases = split(/,/, $tmp_release);
1667                                         # Walk through subreleases and build hash tree
1668                                         my $hash;
1669                                         while(my $tmp_sub_release = pop @sub_releases) {
1670                                                 $hash .= "\{'$tmp_sub_release'\}->";                                            
1671                                         }
1672                                         eval('push @{$fai_classes->'.$hash.'{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";');
1673                                 } else {
1674                                         # A branch, no subrelease
1675                                         push @{$fai_classes->{$tmp_release}->{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";
1676                                 }
1677                         } elsif (!$entry->exists('cn')) {
1678                                 my $tmp_dn= $entry->dn();
1679                                 my $tmp_release = ($1) if $tmp_dn =~ /^(.*?),$fai_base$/;
1681                                 # Skip classes without releases
1682                                 if((!defined($tmp_release)) || length($tmp_release)==0) {
1683                                         next;
1684                                 }
1686                                 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
1687                                         # A Subrelease
1688                                         my @sub_releases= split(/,/, $tmp_release);
1690                                         # Walk through subreleases and build hash tree
1691                                         my $hash;
1692                                         while(my $tmp_sub_release = pop @sub_releases) {
1693                                                 $hash .= "\{'$tmp_sub_release'\}->";                                            
1694                                         }
1695                                         # Remove the last two characters
1696                                         chop($hash);
1697                                         chop($hash);
1699                                         eval('$fai_classes->'.$hash.'= {}');
1700                                 } else {
1701                                         # A branch, no subrelease
1702                                         if(!exists($fai_classes->{$tmp_release})) {
1703                                                 $fai_classes->{$tmp_release} = {};
1704                                         }
1705                                 }
1706                         }
1707                 }
1709                 # The hash is complete, now we can honor the copy-on-write based missing entries
1710                 foreach my $release (keys %$fai_classes) {
1711                         $result->{$release}= deep_copy(apply_fai_inheritance($fai_classes->{$release}));
1712                 }
1713         }
1714         return $result;
1717 sub apply_fai_inheritance {
1718        my $fai_classes = shift || return {};
1719        my $tmp_classes;
1721        # Get the classes from the branch
1722        foreach my $class (keys %{$fai_classes}) {
1723                # Skip subreleases
1724                if($class =~ /^ou=.*$/) {
1725                        next;
1726                } else {
1727                        $tmp_classes->{$class}= deep_copy($fai_classes->{$class});
1728                }
1729        }
1731        # Apply to each subrelease
1732        foreach my $subrelease (keys %{$fai_classes}) {
1733                if($subrelease =~ /ou=/) {
1734                        foreach my $tmp_class (keys %{$tmp_classes}) {
1735                                if(!exists($fai_classes->{$subrelease}->{$tmp_class})) {
1736                                        $fai_classes->{$subrelease}->{$tmp_class} =
1737                                        deep_copy($tmp_classes->{$tmp_class});
1738                                } else {
1739                                        foreach my $type (keys %{$tmp_classes->{$tmp_class}}) {
1740                                                if(!exists($fai_classes->{$subrelease}->{$tmp_class}->{$type})) {
1741                                                        $fai_classes->{$subrelease}->{$tmp_class}->{$type}=
1742                                                        deep_copy($tmp_classes->{$tmp_class}->{$type});
1743                                                }
1744                                        }
1745                                }
1746                        }
1747                }
1748        }
1750        # Find subreleases in deeper levels
1751        foreach my $subrelease (keys %{$fai_classes}) {
1752                if($subrelease =~ /ou=/) {
1753                        foreach my $subsubrelease (keys %{$fai_classes->{$subrelease}}) {
1754                                if($subsubrelease =~ /ou=/) {
1755                                        apply_fai_inheritance($fai_classes->{$subrelease});
1756                                }
1757                        }
1758                }
1759        }
1761        return $fai_classes;
1764 sub get_fai_release_entries {
1765         my $tmp_classes = shift || return;
1766         my $parent = shift || "";
1767         my @result = shift || ();
1769         foreach my $entry (keys %{$tmp_classes}) {
1770                 if(defined($entry)) {
1771                         if($entry =~ /^ou=.*$/) {
1772                                 my $release_name = $entry;
1773                                 $release_name =~ s/ou=//g;
1774                                 if(length($parent)>0) {
1775                                         $release_name = $parent."/".$release_name;
1776                                 }
1777                                 my @bufentries = get_fai_release_entries($tmp_classes->{$entry}, $release_name, @result);
1778                                 foreach my $bufentry(@bufentries) {
1779                                         push @result, $bufentry;
1780                                 }
1781                         } else {
1782                                 my @types = get_fai_types($tmp_classes->{$entry});
1783                                 foreach my $type (@types) {
1784                                         push @result, 
1785                                         {
1786                                                 'class' => $entry,
1787                                                 'type' => $type->{'type'},
1788                                                 'release' => $parent,
1789                                                 'state' => $type->{'state'},
1790                                         };
1791                                 }
1792                         }
1793                 }
1794         }
1796         return @result;
1799 sub deep_copy {
1800         my $this = shift;
1801         if (not ref $this) {
1802                 $this;
1803         } elsif (ref $this eq "ARRAY") {
1804                 [map deep_copy($_), @$this];
1805         } elsif (ref $this eq "HASH") {
1806                 +{map { $_ => deep_copy($this->{$_}) } keys %$this};
1807         } else { die "what type is $_?" }
1811 sub session_run_result {
1812     my ($kernel, $heap, $client_answer) = @_[KERNEL, HEAP, ARG0];    
1813     $kernel->sig(CHLD => "child_reap");
1816 sub session_run_debug {
1817     my $result = $_[ARG0];
1818     print STDERR "$result\n";
1821 sub session_run_done {
1822     my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
1823     delete $heap->{task}->{$task_id};
1826 sub create_sources_list {
1827         my $result="/tmp/gosa_si_tmp_sources_list";
1829         # Remove old file
1830         if(stat($result)) {
1831                 unlink($result);
1832         }
1834         my $fh;
1835         open($fh, ">$result") or return undef;
1836         if(defined($ldap_server_dn) and length($ldap_server_dn) > 0) {
1837                 my $mesg=$ldap_handle->search(
1838                                 base    => $ldap_server_dn,
1839                                 scope   => 'base',
1840                                 attrs   => 'FAIrepository',
1841                                 filter  => 'objectClass=FAIrepositoryServer'
1842                                 );
1843                 if($mesg->count) {
1844                         foreach my $entry(@{$mesg->{'entries'}}) {
1845                                 my ($server, $tag, $release, $sections)= split /\|/, $entry->get_value('FAIrepository');
1846                                 my $line = "deb $server $release";
1847                                 $sections =~ s/,/ /g;
1848                                 $line.= " $sections";
1849                                 print $fh $line."\n";
1850                         }
1851                 }
1852         }
1853         close($fh);
1855         return $result;
1858 sub create_packages_list_db {
1859     my ($sources_file) = @_ || &create_sources_list;
1860     my $line;
1861     daemon_log("INFO: create_packages_list_db: start", 5); 
1863     open(CONFIG, "<$sources_file") or do {
1864         daemon_log( "ERROR: create_packages_list_db: Failed to open '$sources_file'", 1);
1865         return;
1866     };
1867     
1868     # Read lines
1869     while ($line = <CONFIG>){
1870         # Unify
1871         chop($line);
1872         $line =~ s/^\s+//;
1873         $line =~ s/^\s+/ /;
1875         # Strip comments
1876         $line =~ s/#.*$//g;
1878         # Skip empty lines
1879         if ($line =~ /^\s*$/){
1880             next;
1881         }
1883         # Interpret deb line
1884         if ($line =~ /^deb [^\s]+\s[^\s]+\s[^\s]+/){
1885             my( $baseurl, $dist, $sections ) = ($line =~ /^deb\s([^\s]+)\s+([^\s]+)\s+(.*)$/);
1886             my $section;
1887             foreach $section (split(' ', $sections)){
1888                 &parse_package_info( $baseurl, $dist, $section );
1889             }
1890         }
1891     }
1893     close (CONFIG);
1895     daemon_log("INFO: create_packages_list_db: finished", 5); 
1896     return;
1899 sub run_create_packages_list_db {
1900     my ($session, $heap) = @_[SESSION, HEAP];
1901     my $task = POE::Wheel::Run->new(
1902             Program => sub {&create_packages_list_db},
1903             StdoutEvent  => "session_run_result",
1904             StderrEvent  => "session_run_debug",
1905             CloseEvent   => "session_run_done",
1906             );
1907     $heap->{task}->{ $task->ID } = $task;
1910 sub parse_package_info {
1911   my ($baseurl, $dist, $section)= @_;
1912   my ($package);
1914   my ($path) = ($baseurl =~ m%://[^/]*(.*)$%);
1915   $repo_dirs{ "${repo_path}/pool" } = 1;
1917   foreach $package ("Packages.gz"){
1918     daemon_log("DEBUG: create_packages_list: fetch $baseurl, $dist, $section", 7);
1919     get_package( "$baseurl/dists/$dist/$section/binary-$arch/$package", "$outdir/$dist/$section" );
1920     parse_package( "$outdir/$dist/$section", $dist, $path );
1921   }
1922   find(\&cleanup_and_extract, keys( %repo_dirs ) );
1925 sub get_package {
1926   my ($url, $dest)= @_;
1928   my $tpath = dirname($dest);
1929   -d "$tpath" || mkpath "$tpath";
1931   # This is ugly, but I've no time to take a look at "how it works in perl"
1932   if(0 == system("wget '$url' -O '$dest' 2>/dev/null") ) {
1933       system("gunzip -cd '$dest' > '$dest.in'");
1934       unlink($dest);
1935   } else {
1936       daemon_log("ERROR: create_packages_list_db: get_packages: fetching '$url' failed!", 1);
1937   }
1938   return 0;
1941 sub parse_package {
1942     my ($path, $dist, $srv_path)= @_;
1943     my ($package, $version, $section, $description);
1944     my @sql_list;
1945     my $PACKAGES;
1947     if(not stat("$path.in")) {
1948         daemon_log("ERROR: create_packages_list: parse_package: file '$path.in' is not readable",1);
1949         return;
1950     }
1952     open($PACKAGES, "<$path.in");
1953         if(not defined($PACKAGES)) {
1954         daemon_log("ERROR: create_packages_list_db: parse_package: can not open '$path.in'",1); 
1955         return;
1956     }
1958     # Read lines
1959     while (<$PACKAGES>){
1960         my $line = $_;
1961         # Unify
1962         chop($line);
1964         # Use empty lines as a trigger
1965         if ($line =~ /^\s*$/){
1966             my $sql = "INSERT INTO packages_list VALUES ('$dist', '$package', '$version', '$section', '', 'none', '0')";
1967             push(@sql_list, $sql);
1968             $package = "none";
1969             $version = "none";
1970             $section = "none";
1971             $description = "none"; 
1972             next;
1973         }
1975         # Trigger for package name
1976         if ($line =~ /^Package:\s/){
1977             ($package)= ($line =~ /^Package: (.*)$/);
1978             next;
1979         }
1981         # Trigger for version
1982         if ($line =~ /^Version:\s/){
1983             ($version)= ($line =~ /^Version: (.*)$/);
1984             next;
1985         }
1987         # Trigger for description
1988         if ($line =~ /^Description:\s/){
1989             ($description)= ($line =~ /^Description: (.*)$/);
1990             next;
1991         }
1993         # Trigger for section
1994         if ($line =~ /^Section:\s/){
1995             ($section)= ($line =~ /^Section: (.*)$/);
1996             next;
1997         }
1999         # Trigger for filename
2000         if ($line =~ /^Filename:\s/){
2001                 my ($filename) = ($line =~ /^Filename: (.*)$/);
2002                 store_fileinfo( $package, $filename, $dist, $srv_path, $version, $repo_path );
2003                 next;
2004         }
2005     }
2007     close( $PACKAGES );
2008     unlink( "$path.in" );
2009     
2010     $packages_list_db->exec_statementlist(\@sql_list);
2013 sub store_fileinfo {
2014   my( $package, $file, $dist, $path, $vers, $srvdir) = @_;
2016   my %fileinfo = (
2017     'package' => $package,
2018     'dist' => $dist,
2019     'version' => $vers,
2020   );
2022   $repo_files{ "${srvdir}/$file" } = \%fileinfo;
2025 sub cleanup_and_extract {
2026   my $fileinfo = $repo_files{ $File::Find::name };
2028   if( defined $fileinfo ) {
2030     my $dir = "$outdir/$fileinfo->{ 'dist' }/debconf.d";
2031     my $sql;
2032     my $package = $fileinfo->{ 'package' };
2033     my $newver = $fileinfo->{ 'version' };
2035     mkpath($dir);
2036     system( "dpkg -e '$File::Find::name' '$dir/DEBIAN'" );
2038     if( -f "$dir/DEBIAN/templates" ) {
2040       daemon_log("DEBUG: Found debconf templates in '$package' - $newver", 5);
2042       my $tmpl= "";
2043       {
2044           local $/=undef;
2045           open FILE, "$dir/DEBIAN/templates";
2046           $tmpl = &encode_base64(<FILE>);
2047           close FILE;
2048       }
2049       rmtree("$dir/DEBIAN/templates");
2051       $sql= "update $main::packages_list_tn set template = '$tmpl' where package = '$package' and version = '$newver';";
2053     } else {
2054       $sql= "update $main::packages_list_tn set template = '' where package = '$package' and version = '$newver';";
2055     }
2057     my $res= $main::packages_list_db->update_dbentry($sql);
2058   }
2062 #==== MAIN = main ==============================================================
2063 #  parse commandline options
2064 Getopt::Long::Configure( "bundling" );
2065 GetOptions("h|help" => \&usage,
2066         "c|config=s" => \$cfg_file,
2067         "f|foreground" => \$foreground,
2068         "v|verbose+" => \$verbose,
2069         "no-bus+" => \$no_bus,
2070         "no-arp+" => \$no_arp,
2071            );
2073 #  read and set config parameters
2074 &check_cmdline_param ;
2075 &read_configfile;
2076 &check_pid;
2078 $SIG{CHLD} = 'IGNORE';
2080 # forward error messages to logfile
2081 if( ! $foreground ) {
2082   open( STDIN,  '+>/dev/null' );
2083   open( STDOUT, '+>&STDIN'    );
2084   open( STDERR, '+>&STDIN'    );
2087 # Just fork, if we are not in foreground mode
2088 if( ! $foreground ) { 
2089     chdir '/'                 or die "Can't chdir to /: $!";
2090     $pid = fork;
2091     setsid                    or die "Can't start a new session: $!";
2092     umask 0;
2093 } else { 
2094     $pid = $$; 
2097 # Do something useful - put our PID into the pid_file
2098 if( 0 != $pid ) {
2099     open( LOCK_FILE, ">$pid_file" );
2100     print LOCK_FILE "$pid\n";
2101     close( LOCK_FILE );
2102     if( !$foreground ) { 
2103         exit( 0 ) 
2104     };
2107 daemon_log(" ", 1);
2108 daemon_log("$0 started!", 1);
2110 if ($no_bus > 0) {
2111     $bus_activ = "false"
2116 # delete old DBsqlite lock files
2117 #unlink('/tmp/gosa_si_lock*');
2119 # connect to gosa-si job queue
2120 $job_db = GOSA::DBsqlite->new($job_queue_file_name);
2121 $job_db->create_table($job_queue_tn, \@job_queue_col_names);
2123 # connect to known_clients_db
2124 $known_clients_db = GOSA::DBsqlite->new($known_clients_file_name);
2125 $known_clients_db->create_table($known_clients_tn, \@known_clients_col_names);
2127 # connect to known_server_db
2128 $known_server_db = GOSA::DBsqlite->new($known_server_file_name);
2129 $known_server_db->create_table($known_server_tn, \@known_server_col_names);
2131 # connect to login_usr_db
2132 $login_users_db = GOSA::DBsqlite->new($login_users_file_name);
2133 $login_users_db->create_table($login_users_tn, \@login_users_col_names);
2135 # connect to fai_server_db and fai_release_db
2136 $fai_server_db = GOSA::DBsqlite->new($fai_server_file_name);
2137 $fai_server_db->create_table($fai_server_tn, \@fai_server_col_names);
2138 $fai_server_db->create_table($fai_release_tn, \@fai_release_col_names);
2140 # connect to packages_list_db
2141 unlink($packages_list_file_name);
2142 $packages_list_db = GOSA::DBsqlite->new($packages_list_file_name);
2143 $packages_list_db->create_table($packages_list_tn, \@packages_list_col_names);
2145 # connect to messaging_db
2146 $messaging_db = GOSA::DBsqlite->new($messaging_file_name);
2147 $messaging_db->create_table($messaging_tn, \@messaging_col_names);
2150 # create xml object used for en/decrypting
2151 $xml = new XML::Simple();
2153 # create socket for incoming xml messages
2155 POE::Component::Server::TCP->new(
2156         Port => $server_port,
2157         ClientInput => sub {
2158         my ($kernel, $input) = @_[KERNEL, ARG0];
2159         push(@tasks, $input);
2160         $kernel->yield("next_task");
2161         },
2162     InlineStates => {
2163         next_task => \&next_task,
2164         task_result => \&handle_task_result,
2165         task_done   => \&handle_task_done,
2166         task_debug  => \&handle_task_debug,
2167         child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!"  },
2168     }
2169 );
2171 daemon_log("start socket for incoming xml messages at port '$server_port' ", 1);
2173 # create session for repeatedly checking the job queue for jobs
2174 POE::Session->create(
2175         inline_states => {
2176                 _start => \&_start,
2177                 sig_handler => \&sig_handler,
2178                 watch_for_new_jobs => \&watch_for_new_jobs,
2179         watch_for_done_jobs => \&watch_for_done_jobs,
2180         create_packages_list_db => \&run_create_packages_list_db,
2181         create_fai_server_db => \&run_create_fai_server_db,
2182         create_fai_release_db => \&run_create_fai_release_db,
2183         session_run_result => \&session_run_result,
2184         session_run_debug => \&session_run_debug,
2185         session_run_done => \&session_run_done,
2186         child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!"  },
2187         }
2188 );
2191 # import all modules
2192 &import_modules;
2194 # check wether all modules are gosa-si valid passwd check
2196 POE::Kernel->run();
2197 exit;