4330970853c444832319b31ba1135a9251bfb4f8
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 #===============================================================================
24 # TODO
25 #
26 # max_children wird momentan nicht mehr verwendet, jede eingehende nachricht bekommt ein eigenes POE child
28 use strict;
29 use warnings;
30 use Getopt::Long;
31 use Config::IniFiles;
32 use POSIX;
34 use Fcntl;
35 use IO::Socket::INET;
36 use IO::Handle;
37 use IO::Select;
38 use Symbol qw(qualify_to_ref);
39 use Crypt::Rijndael;
40 use MIME::Base64;
41 use Digest::MD5 qw(md5 md5_hex md5_base64);
42 use XML::Simple;
43 use Data::Dumper;
44 use Sys::Syslog qw( :DEFAULT setlogsock);
45 use Cwd;
46 use File::Spec;
47 use File::Basename;
48 use File::Find;
49 use File::Copy;
50 use File::Path;
51 use GOSA::DBsqlite;
52 use GOSA::GosaSupportDaemon;
53 use POE qw(Component::Server::TCP Wheel::Run Filter::Reference);
54 use Net::LDAP;
55 use Net::LDAP::Util qw(:escape);
57 my $modules_path = "/usr/lib/gosa-si/modules";
58 use lib "/usr/lib/gosa-si/modules";
60 # revision number of server and program name
61 my $server_version = '$HeadURL: https://oss.gonicus.de/repositories/gosa/trunk/gosa-si/gosa-si-server $:$Rev: 10826 $';
62 my $server_headURL;
63 my $server_revision;
64 my $server_status;
65 our $prg= basename($0);
67 our $global_kernel;
68 my (%cfg_defaults, $foreground, $verbose, $ping_timeout);
69 my ($bus_activ, $bus, $msg_to_bus, $bus_cipher);
70 my ($server);
71 my ($gosa_server, $job_queue_timeout, $job_queue_loop_delay);
72 my ($messaging_db_loop_delay);
73 my ($known_modules);
74 my ($pid_file, $procid, $pid, $log_file);
75 my ($arp_activ, $arp_fifo);
76 my ($xml);
77 my $sources_list;
78 my $max_clients;
79 my %repo_files=();
80 my $repo_path;
81 my %repo_dirs=();
82 # variables declared in config file are always set to 'our'
83 our (%cfg_defaults, $log_file, $pid_file,
84 $server_ip, $server_port, $SIPackages_key,
85 $arp_activ, $gosa_unit_tag,
86 $GosaPackages_key, $gosa_ip, $gosa_port, $gosa_timeout,
87 );
89 # additional variable which should be globaly accessable
90 our $server_address;
91 our $server_mac_address;
92 our $bus_address;
93 our $gosa_address;
94 our $no_bus;
95 our $no_arp;
96 our $verbose;
97 our $forground;
98 our $cfg_file;
99 our ($ldap_uri, $ldap_base, $ldap_admin_dn, $ldap_admin_password, $ldap_server_dn);
102 # specifies the verbosity of the daemon_log
103 $verbose = 0 ;
105 # if foreground is not null, script will be not forked to background
106 $foreground = 0 ;
108 # specifies the timeout seconds while checking the online status of a registrating client
109 $ping_timeout = 5;
111 $no_bus = 0;
112 $bus_activ = "true";
113 $no_arp = 0;
114 my $packages_list_under_construction = "/tmp/packages_list_creation_in_progress";
115 my @packages_list_statements;
116 my $watch_for_new_jobs_in_progress = 0;
118 # holds all incoming decrypted messages
119 our $incoming_db;
120 our $incoming_tn = 'incoming';
121 my $incoming_file_name;
122 my @incoming_col_names = ("id INTEGER PRIMARY KEY",
123 "timestamp DEFAULT 'none'",
124 "headertag DEFAULT 'none'",
125 "targettag DEFAULT 'none'",
126 "xmlmessage DEFAULT 'none'",
127 "module DEFAULT 'none'",
128 );
130 # holds all gosa jobs
131 our $job_db;
132 our $job_queue_tn = 'jobs';
133 my $job_queue_file_name;
134 my @job_queue_col_names = ("id INTEGER PRIMARY KEY",
135 "timestamp DEFAULT 'none'",
136 "status DEFAULT 'none'",
137 "result DEFAULT 'none'",
138 "progress DEFAULT 'none'",
139 "headertag DEFAULT 'none'",
140 "targettag DEFAULT 'none'",
141 "xmlmessage DEFAULT 'none'",
142 "macaddress DEFAULT 'none'",
143 "plainname DEFAULT 'none'",
144 );
146 # holds all other gosa-sd as well as the gosa-sd-bus
147 our $known_server_db;
148 our $known_server_tn = "known_server";
149 my $known_server_file_name;
150 my @known_server_col_names = ("hostname", "status", "hostkey", "timestamp");
152 # holds all registrated clients
153 our $known_clients_db;
154 our $known_clients_tn = "known_clients";
155 my $known_clients_file_name;
156 my @known_clients_col_names = ("hostname", "status", "hostkey", "timestamp", "macaddress", "events");
158 # holds all logged in user at each client
159 our $login_users_db;
160 our $login_users_tn = "login_users";
161 my $login_users_file_name;
162 my @login_users_col_names = ("client", "user", "timestamp");
164 # holds all fai server, the debian release and tag
165 our $fai_server_db;
166 our $fai_server_tn = "fai_server";
167 my $fai_server_file_name;
168 our @fai_server_col_names = ("timestamp", "server", "release", "sections", "tag");
170 our $fai_release_db;
171 our $fai_release_tn = "fai_release";
172 my $fai_release_file_name;
173 our @fai_release_col_names = ("timestamp", "release", "class", "type", "state");
175 # holds all packages available from different repositories
176 our $packages_list_db;
177 our $packages_list_tn = "packages_list";
178 my $packages_list_file_name;
179 our @packages_list_col_names = ("distribution", "package", "version", "section", "description", "template", "timestamp");
180 my $outdir = "/tmp/packages_list_db";
181 my $arch = "i386";
183 # holds all messages which should be delivered to a user
184 our $messaging_db;
185 our $messaging_tn = "messaging";
186 our @messaging_col_names = ("id INTEGER", "subject", "message_from", "message_to",
187 "flag", "direction", "delivery_time", "message", "timestamp" );
188 my $messaging_file_name;
190 # path to directory to store client install log files
191 our $client_fai_log_dir = "/var/log/fai";
193 # queue which stores taskes until one of the $max_children children are ready to process the task
194 my @tasks = qw();
195 my @msgs_to_decrypt = qw();
196 my $max_children = 2;
199 %cfg_defaults = (
200 "general" => {
201 "log-file" => [\$log_file, "/var/run/".$prg.".log"],
202 "pid-file" => [\$pid_file, "/var/run/".$prg.".pid"],
203 },
204 "bus" => {
205 "activ" => [\$bus_activ, "true"],
206 },
207 "server" => {
208 "port" => [\$server_port, "20081"],
209 "known-clients" => [\$known_clients_file_name, '/var/lib/gosa-si/clients.db' ],
210 "known-servers" => [\$known_server_file_name, '/var/lib/gosa-si/servers.db'],
211 "incoming" => [\$incoming_file_name, '/var/lib/gosa-si/incoming.db'],
212 "login-users" => [\$login_users_file_name, '/var/lib/gosa-si/users.db'],
213 "fai-server" => [\$fai_server_file_name, '/var/lib/gosa-si/fai_server.db'],
214 "fai-release" => [\$fai_release_file_name, '/var/lib/gosa-si/fai_release.db'],
215 "packages-list" => [\$packages_list_file_name, '/var/lib/gosa-si/packages.db'],
216 "messaging" => [\$messaging_file_name, '/var/lib/gosa-si/messaging.db'],
217 "source-list" => [\$sources_list, '/etc/apt/sources.list'],
218 "repo-path" => [\$repo_path, '/srv/www/repository'],
219 "ldap-uri" => [\$ldap_uri, ""],
220 "ldap-base" => [\$ldap_base, ""],
221 "ldap-admin-dn" => [\$ldap_admin_dn, ""],
222 "ldap-admin-password" => [\$ldap_admin_password, ""],
223 "gosa-unit-tag" => [\$gosa_unit_tag, ""],
224 "max-clients" => [\$max_clients, 10],
225 },
226 "GOsaPackages" => {
227 "ip" => [\$gosa_ip, "0.0.0.0"],
228 "port" => [\$gosa_port, "20082"],
229 "job-queue" => [\$job_queue_file_name, '/var/lib/gosa-si/jobs.db'],
230 "job-queue-loop-delay" => [\$job_queue_loop_delay, 3],
231 "messaging-db-loop-delay" => [\$messaging_db_loop_delay, 3],
232 "key" => [\$GosaPackages_key, "none"],
233 },
234 "SIPackages" => {
235 "key" => [\$SIPackages_key, "none"],
236 },
237 );
240 #=== FUNCTION ================================================================
241 # NAME: usage
242 # PARAMETERS: nothing
243 # RETURNS: nothing
244 # DESCRIPTION: print out usage text to STDERR
245 #===============================================================================
246 sub usage {
247 print STDERR << "EOF" ;
248 usage: $prg [-hvf] [-c config]
250 -h : this (help) message
251 -c <file> : config file
252 -f : foreground, process will not be forked to background
253 -v : be verbose (multiple to increase verbosity)
254 -no-bus : starts $prg without connection to bus
255 -no-arp : starts $prg without connection to arp module
257 EOF
258 print "\n" ;
259 }
262 #=== FUNCTION ================================================================
263 # NAME: read_configfile
264 # PARAMETERS: cfg_file - string -
265 # RETURNS: nothing
266 # DESCRIPTION: read cfg_file and set variables
267 #===============================================================================
268 sub read_configfile {
269 my $cfg;
270 if( defined( $cfg_file) && ( (-s $cfg_file) > 0 )) {
271 if( -r $cfg_file ) {
272 $cfg = Config::IniFiles->new( -file => $cfg_file );
273 } else {
274 print STDERR "Couldn't read config file!\n";
275 }
276 } else {
277 $cfg = Config::IniFiles->new() ;
278 }
279 foreach my $section (keys %cfg_defaults) {
280 foreach my $param (keys %{$cfg_defaults{ $section }}) {
281 my $pinfo = $cfg_defaults{ $section }{ $param };
282 ${@$pinfo[ 0 ]} = $cfg->val( $section, $param, @$pinfo[ 1 ] );
283 }
284 }
285 }
288 #=== FUNCTION ================================================================
289 # NAME: logging
290 # PARAMETERS: level - string - default 'info'
291 # msg - string -
292 # facility - string - default 'LOG_DAEMON'
293 # RETURNS: nothing
294 # DESCRIPTION: function for logging
295 #===============================================================================
296 sub daemon_log {
297 # log into log_file
298 my( $msg, $level ) = @_;
299 if(not defined $msg) { return }
300 if(not defined $level) { $level = 1 }
301 if(defined $log_file){
302 open(LOG_HANDLE, ">>$log_file");
303 if(not defined open( LOG_HANDLE, ">>$log_file" )) {
304 print STDERR "cannot open $log_file: $!";
305 return }
306 chomp($msg);
307 $msg =~s/\n//g; # no newlines are allowed in log messages, this is important for later log parsing
308 if($level <= $verbose){
309 my ($seconds, $minutes, $hours, $monthday, $month,
310 $year, $weekday, $yearday, $sommertime) = localtime(time);
311 $hours = $hours < 10 ? $hours = "0".$hours : $hours;
312 $minutes = $minutes < 10 ? $minutes = "0".$minutes : $minutes;
313 $seconds = $seconds < 10 ? $seconds = "0".$seconds : $seconds;
314 my @monthnames = ("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec");
315 $month = $monthnames[$month];
316 $monthday = $monthday < 10 ? $monthday = "0".$monthday : $monthday;
317 $year+=1900;
318 my $name = $prg;
320 my $log_msg = "$month $monthday $hours:$minutes:$seconds $name $msg\n";
321 print LOG_HANDLE $log_msg;
322 if( $foreground ) {
323 print STDERR $log_msg;
324 }
325 }
326 close( LOG_HANDLE );
327 }
328 }
331 #=== FUNCTION ================================================================
332 # NAME: check_cmdline_param
333 # PARAMETERS: nothing
334 # RETURNS: nothing
335 # DESCRIPTION: validates commandline parameter
336 #===============================================================================
337 sub check_cmdline_param () {
338 my $err_config;
339 my $err_counter = 0;
340 if(not defined($cfg_file)) {
341 $cfg_file = "/etc/gosa-si/server.conf";
342 if(! -r $cfg_file) {
343 $err_config = "please specify a config file";
344 $err_counter += 1;
345 }
346 }
347 if( $err_counter > 0 ) {
348 &usage( "", 1 );
349 if( defined( $err_config)) { print STDERR "$err_config\n"}
350 print STDERR "\n";
351 exit( -1 );
352 }
353 }
356 #=== FUNCTION ================================================================
357 # NAME: check_pid
358 # PARAMETERS: nothing
359 # RETURNS: nothing
360 # DESCRIPTION: handels pid processing
361 #===============================================================================
362 sub check_pid {
363 $pid = -1;
364 # Check, if we are already running
365 if( open(LOCK_FILE, "<$pid_file") ) {
366 $pid = <LOCK_FILE>;
367 if( defined $pid ) {
368 chomp( $pid );
369 if( -f "/proc/$pid/stat" ) {
370 my($stat) = `cat /proc/$pid/stat` =~ m/$pid \((.+)\).*/;
371 if( $stat ) {
372 daemon_log("ERROR: Already running",1);
373 close( LOCK_FILE );
374 exit -1;
375 }
376 }
377 }
378 close( LOCK_FILE );
379 unlink( $pid_file );
380 }
382 # create a syslog msg if it is not to possible to open PID file
383 if (not sysopen(LOCK_FILE, $pid_file, O_WRONLY|O_CREAT|O_EXCL, 0644)) {
384 my($msg) = "Couldn't obtain lockfile '$pid_file' ";
385 if (open(LOCK_FILE, '<', $pid_file)
386 && ($pid = <LOCK_FILE>))
387 {
388 chomp($pid);
389 $msg .= "(PID $pid)\n";
390 } else {
391 $msg .= "(unable to read PID)\n";
392 }
393 if( ! ($foreground) ) {
394 openlog( $0, "cons,pid", "daemon" );
395 syslog( "warning", $msg );
396 closelog();
397 }
398 else {
399 print( STDERR " $msg " );
400 }
401 exit( -1 );
402 }
403 }
405 #=== FUNCTION ================================================================
406 # NAME: import_modules
407 # PARAMETERS: module_path - string - abs. path to the directory the modules
408 # are stored
409 # RETURNS: nothing
410 # DESCRIPTION: each file in module_path which ends with '.pm' and activation
411 # state is on is imported by "require 'file';"
412 #===============================================================================
413 sub import_modules {
414 daemon_log(" ", 1);
416 if (not -e $modules_path) {
417 daemon_log("ERROR: cannot find directory or directory is not readable: $modules_path", 1);
418 }
420 opendir (DIR, $modules_path) or die "ERROR while loading modules from directory $modules_path : $!\n";
421 while (defined (my $file = readdir (DIR))) {
422 if (not $file =~ /(\S*?).pm$/) {
423 next;
424 }
425 my $mod_name = $1;
427 if( $file =~ /ArpHandler.pm/ ) {
428 if( $no_arp > 0 ) {
429 next;
430 }
431 }
433 eval { require $file; };
434 if ($@) {
435 daemon_log("ERROR: gosa-si-server could not load module $file", 1);
436 daemon_log("$@", 5);
437 } else {
438 my $info = eval($mod_name.'::get_module_info()');
439 # Only load module if get_module_info() returns a non-null object
440 if( $info ) {
441 my ($input_address, $input_key, $input, $input_active, $input_type) = @{$info};
442 $known_modules->{$mod_name} = $info;
443 daemon_log("INFO: module $mod_name loaded", 5);
444 }
445 }
446 }
447 close (DIR);
448 }
451 #=== FUNCTION ================================================================
452 # NAME: sig_int_handler
453 # PARAMETERS: signal - string - signal arose from system
454 # RETURNS: noting
455 # DESCRIPTION: handels tasks to be done befor signal becomes active
456 #===============================================================================
457 sub sig_int_handler {
458 my ($signal) = @_;
460 # if (defined($ldap_handle)) {
461 # $ldap_handle->disconnect;
462 # }
463 # TODO alle verbliebenden ldap verbindungen aus allen heaps beenden
466 daemon_log("shutting down gosa-si-server", 1);
467 system("kill `ps -C gosa-si-server -o pid=`");
468 }
469 $SIG{INT} = \&sig_int_handler;
472 sub check_key_and_xml_validity {
473 my ($crypted_msg, $module_key, $session_id) = @_;
474 my $msg;
475 my $msg_hash;
476 my $error_string;
477 eval{
478 $msg = &decrypt_msg($crypted_msg, $module_key);
480 if ($msg =~ /<xml>/i){
481 $msg =~ s/\s+/ /g; # just for better daemon_log
482 daemon_log("$session_id DEBUG: decrypted_msg: \n$msg", 8);
483 $msg_hash = $xml->XMLin($msg, ForceArray=>1);
485 ##############
486 # check header
487 if( not exists $msg_hash->{'header'} ) { die "no header specified"; }
488 my $header_l = $msg_hash->{'header'};
489 if( 1 > @{$header_l} ) { die 'empty header tag'; }
490 if( 1 < @{$header_l} ) { die 'more than one header specified'; }
491 my $header = @{$header_l}[0];
492 if( 0 == length $header) { die 'empty string in header tag'; }
494 ##############
495 # check source
496 if( not exists $msg_hash->{'source'} ) { die "no source specified"; }
497 my $source_l = $msg_hash->{'source'};
498 if( 1 > @{$source_l} ) { die 'empty source tag'; }
499 if( 1 < @{$source_l} ) { die 'more than one source specified'; }
500 my $source = @{$source_l}[0];
501 if( 0 == length $source) { die 'source error'; }
503 ##############
504 # check target
505 if( not exists $msg_hash->{'target'} ) { die "no target specified"; }
506 my $target_l = $msg_hash->{'target'};
507 if( 1 > @{$target_l} ) { die 'empty target tag'; }
508 }
509 };
510 if($@) {
511 daemon_log("$session_id DEBUG: do not understand the message: $@", 7);
512 $msg = undef;
513 $msg_hash = undef;
514 }
516 return ($msg, $msg_hash);
517 }
520 sub check_outgoing_xml_validity {
521 my ($msg) = @_;
523 my $msg_hash;
524 eval{
525 $msg_hash = $xml->XMLin($msg, ForceArray=>1);
527 ##############
528 # check header
529 my $header_l = $msg_hash->{'header'};
530 if( 1 != @{$header_l} ) {
531 die 'no or more than one headers specified';
532 }
533 my $header = @{$header_l}[0];
534 if( 0 == length $header) {
535 die 'header has length 0';
536 }
538 ##############
539 # check source
540 my $source_l = $msg_hash->{'source'};
541 if( 1 != @{$source_l} ) {
542 die 'no or more than 1 sources specified';
543 }
544 my $source = @{$source_l}[0];
545 if( 0 == length $source) {
546 die 'source has length 0';
547 }
548 unless( $source =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
549 $source =~ /^GOSA$/i ) {
550 die "source '$source' is neither a complete ip-address with port nor 'GOSA'";
551 }
553 ##############
554 # check target
555 my $target_l = $msg_hash->{'target'};
556 if( 0 == @{$target_l} ) {
557 die "no targets specified";
558 }
559 foreach my $target (@$target_l) {
560 if( 0 == length $target) {
561 die "target has length 0";
562 }
563 unless( $target =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
564 $target =~ /^GOSA$/i ||
565 $target =~ /^\*$/ ||
566 $target =~ /KNOWN_SERVER/i ||
567 $target =~ /JOBDB/i ||
568 $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 ){
569 die "target '$target' is not a complete ip-address with port or a valid target name or a mac-address";
570 }
571 }
572 };
573 if($@) {
574 daemon_log("WARNING: outgoing msg is not gosa-si envelope conform", 5);
575 daemon_log("$@ ".(defined($msg) && length($msg)>0)?$msg:"Empty Message", 8);
576 $msg_hash = undef;
577 }
579 return ($msg_hash);
580 }
583 sub input_from_known_server {
584 my ($input, $remote_ip, $session_id) = @_ ;
585 my ($msg, $msg_hash, $module);
587 my $sql_statement= "SELECT * FROM known_server";
588 my $query_res = $known_server_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") {
593 next;
594 }
595 my $host_key = $hit->{hostkey};
596 daemon_log("$session_id DEBUG: input_from_known_server: host_name: $host_name", 7);
597 daemon_log("DEBUG: input_from_known_server: host_key: $host_key", 7);
599 # check if module can open msg envelope with module key
600 my ($tmp_msg, $tmp_msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
601 if( (!$tmp_msg) || (!$tmp_msg_hash) ) {
602 daemon_log("$session_id DEBUG: input_from_known_server: deciphering raise error", 7);
603 daemon_log("$@", 8);
604 next;
605 }
606 else {
607 $msg = $tmp_msg;
608 $msg_hash = $tmp_msg_hash;
609 $module = "SIPackages";
610 last;
611 }
612 }
614 if( (!$msg) || (!$msg_hash) || (!$module) ) {
615 daemon_log("$session_id DEBUG: Incoming message is not from a known server", 7);
616 }
618 return ($msg, $msg_hash, $module);
619 }
622 sub input_from_known_client {
623 my ($input, $remote_ip, $session_id) = @_ ;
624 my ($msg, $msg_hash, $module);
626 my $sql_statement= "SELECT * FROM known_clients";
627 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
628 while( my ($hit_num, $hit) = each %{ $query_res } ) {
629 my $host_name = $hit->{hostname};
630 if( not $host_name =~ /^$remote_ip:\d*$/) {
631 next;
632 }
633 my $host_key = $hit->{hostkey};
634 &daemon_log("$session_id DEBUG: input_from_known_client: host_name: $host_name", 7);
635 &daemon_log("$session_id DEBUG: input_from_known_client: host_key: $host_key", 7);
637 # check if module can open msg envelope with module key
638 ($msg, $msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
640 if( (!$msg) || (!$msg_hash) ) {
641 &daemon_log("$session_id DEGUG: input_from_known_client: deciphering raise error", 7);
642 &daemon_log("$@", 8);
643 next;
644 }
645 else {
646 $module = "SIPackages";
647 last;
648 }
649 }
651 if( (!$msg) || (!$msg_hash) || (!$module) ) {
652 &daemon_log("$session_id DEBUG: Incoming message is not from a known client", 7);
653 }
655 return ($msg, $msg_hash, $module);
656 }
659 sub input_from_unknown_host {
660 no strict "refs";
661 my ($input, $session_id) = @_ ;
662 my ($msg, $msg_hash, $module);
663 my $error_string;
665 my %act_modules = %$known_modules;
667 while( my ($mod, $info) = each(%act_modules)) {
669 # check a key exists for this module
670 my $module_key = ${$mod."_key"};
671 if( not defined $module_key ) {
672 if( $mod eq 'ArpHandler' ) {
673 next;
674 }
675 daemon_log("$session_id ERROR: no key specified in config file for $mod", 1);
676 next;
677 }
678 daemon_log("$session_id DEBUG: $mod: $module_key", 7);
680 # check if module can open msg envelope with module key
681 ($msg, $msg_hash) = &check_key_and_xml_validity($input, $module_key, $session_id);
682 if( (not defined $msg) || (not defined $msg_hash) ) {
683 next;
684 }
685 else {
686 $module = $mod;
687 last;
688 }
689 }
691 if( (!$msg) || (!$msg_hash) || (!$module)) {
692 daemon_log("$session_id DEBUG: Incoming message is not from an unknown host", 7);
693 }
695 return ($msg, $msg_hash, $module);
696 }
699 sub create_ciphering {
700 my ($passwd) = @_;
701 if((!defined($passwd)) || length($passwd)==0) {
702 $passwd = "";
703 }
704 $passwd = substr(md5_hex("$passwd") x 32, 0, 32);
705 my $iv = substr(md5_hex('GONICUS GmbH'),0, 16);
706 my $my_cipher = Crypt::Rijndael->new($passwd , Crypt::Rijndael::MODE_CBC());
707 $my_cipher->set_iv($iv);
708 return $my_cipher;
709 }
712 sub encrypt_msg {
713 my ($msg, $key) = @_;
714 my $my_cipher = &create_ciphering($key);
715 my $len;
716 {
717 use bytes;
718 $len= 16-length($msg)%16;
719 }
720 $msg = "\0"x($len).$msg;
721 $msg = $my_cipher->encrypt($msg);
722 chomp($msg = &encode_base64($msg));
723 # there are no newlines allowed inside msg
724 $msg=~ s/\n//g;
725 return $msg;
726 }
729 sub decrypt_msg {
731 my ($msg, $key) = @_ ;
732 $msg = &decode_base64($msg);
733 my $my_cipher = &create_ciphering($key);
734 $msg = $my_cipher->decrypt($msg);
735 $msg =~ s/\0*//g;
736 return $msg;
737 }
740 sub get_encrypt_key {
741 my ($target) = @_ ;
742 my $encrypt_key;
743 my $error = 0;
745 # target can be in known_server
746 if( not defined $encrypt_key ) {
747 my $sql_statement= "SELECT * FROM known_server WHERE hostname='$target'";
748 my $query_res = $known_server_db->select_dbentry( $sql_statement );
749 while( my ($hit_num, $hit) = each %{ $query_res } ) {
750 my $host_name = $hit->{hostname};
751 if( $host_name ne $target ) {
752 next;
753 }
754 $encrypt_key = $hit->{hostkey};
755 last;
756 }
757 }
759 # target can be in known_client
760 if( not defined $encrypt_key ) {
761 my $sql_statement= "SELECT * FROM known_clients WHERE hostname='$target'";
762 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
763 while( my ($hit_num, $hit) = each %{ $query_res } ) {
764 my $host_name = $hit->{hostname};
765 if( $host_name ne $target ) {
766 next;
767 }
768 $encrypt_key = $hit->{hostkey};
769 last;
770 }
771 }
773 return $encrypt_key;
774 }
777 #=== FUNCTION ================================================================
778 # NAME: open_socket
779 # PARAMETERS: PeerAddr string something like 192.168.1.1 or 192.168.1.1:10000
780 # [PeerPort] string necessary if port not appended by PeerAddr
781 # RETURNS: socket IO::Socket::INET
782 # DESCRIPTION: open a socket to PeerAddr
783 #===============================================================================
784 sub open_socket {
785 my ($PeerAddr, $PeerPort) = @_ ;
786 if(defined($PeerPort)){
787 $PeerAddr = $PeerAddr.":".$PeerPort;
788 }
789 my $socket;
790 $socket = new IO::Socket::INET(PeerAddr => $PeerAddr,
791 Porto => "tcp",
792 Type => SOCK_STREAM,
793 Timeout => 5,
794 );
795 if(not defined $socket) {
796 return;
797 }
798 # &daemon_log("DEBUG: open_socket: $PeerAddr", 7);
799 return $socket;
800 }
803 #=== FUNCTION ================================================================
804 # NAME: get_ip
805 # PARAMETERS: interface name (i.e. eth0)
806 # RETURNS: (ip address)
807 # DESCRIPTION: Uses ioctl to get ip address directly from system.
808 #===============================================================================
809 sub get_ip {
810 my $ifreq= shift;
811 my $result= "";
812 my $SIOCGIFADDR= 0x8915; # man 2 ioctl_list
813 my $proto= getprotobyname('ip');
815 socket SOCKET, PF_INET, SOCK_DGRAM, $proto
816 or die "socket: $!";
818 if(ioctl SOCKET, $SIOCGIFADDR, $ifreq) {
819 my ($if, $sin) = unpack 'a16 a16', $ifreq;
820 my ($port, $addr) = sockaddr_in $sin;
821 my $ip = inet_ntoa $addr;
823 if ($ip && length($ip) > 0) {
824 $result = $ip;
825 }
826 }
828 return $result;
829 }
832 sub get_local_ip_for_remote_ip {
833 my $remote_ip= shift;
834 my $result="0.0.0.0";
836 if($remote_ip =~ /^(\d\d?\d?\.){3}\d\d?\d?$/) {
837 if($remote_ip eq "127.0.0.1") {
838 $result = "127.0.0.1";
839 } else {
840 my $PROC_NET_ROUTE= ('/proc/net/route');
842 open(PROC_NET_ROUTE, "<$PROC_NET_ROUTE")
843 or die "Could not open $PROC_NET_ROUTE";
845 my @ifs = <PROC_NET_ROUTE>;
847 close(PROC_NET_ROUTE);
849 # Eat header line
850 shift @ifs;
851 chomp @ifs;
852 foreach my $line(@ifs) {
853 my ($Iface,$Destination,$Gateway,$Flags,$RefCnt,$Use,$Metric,$Mask,$MTU,$Window,$IRTT)=split(/\s/, $line);
854 my $destination;
855 my $mask;
856 my ($d,$c,$b,$a)=unpack('a2 a2 a2 a2', $Destination);
857 $destination= sprintf("%d.%d.%d.%d", hex($a), hex($b), hex($c), hex($d));
858 ($d,$c,$b,$a)=unpack('a2 a2 a2 a2', $Mask);
859 $mask= sprintf("%d.%d.%d.%d", hex($a), hex($b), hex($c), hex($d));
860 if(new NetAddr::IP($remote_ip)->within(new NetAddr::IP($destination, $mask))) {
861 # destination matches route, save mac and exit
862 $result= &get_ip($Iface);
863 last;
864 }
865 }
866 }
867 } else {
868 daemon_log("get_local_ip_for_remote_ip was called with a non-ip parameter: $remote_ip", 1);
869 }
870 return $result;
871 }
874 sub send_msg_to_target {
875 my ($msg, $address, $encrypt_key, $msg_header, $session_id) = @_ ;
876 my $error = 0;
877 my $header;
878 my $new_status;
879 my $act_status;
880 my ($sql_statement, $res);
882 if( $msg_header ) {
883 $header = "'$msg_header'-";
884 } else {
885 $header = "";
886 }
888 # Patch the source ip
889 if($msg =~ /<source>0\.0\.0\.0:\d*?<\/source>/) {
890 my $remote_ip = &get_local_ip_for_remote_ip(sprintf("%s", $address =~ /^([0-9\.]*?):.*$/));
891 $msg =~ s/<source>(0\.0\.0\.0):(\d*?)<\/source>/<source>$remote_ip:$2<\/source>/s;
892 }
894 # encrypt xml msg
895 my $crypted_msg = &encrypt_msg($msg, $encrypt_key);
897 # opensocket
898 my $socket = &open_socket($address);
899 if( !$socket ) {
900 daemon_log("$session_id ERROR: cannot send ".$header."msg to $address , host not reachable", 1);
901 $error++;
902 }
904 if( $error == 0 ) {
905 # send xml msg
906 print $socket $crypted_msg."\n";
908 daemon_log("$session_id INFO: send ".$header."msg to $address", 5);
909 #daemon_log("DEBUG: message:\n$msg", 9);
911 }
913 # close socket in any case
914 if( $socket ) {
915 close $socket;
916 }
918 if( $error > 0 ) { $new_status = "down"; }
919 else { $new_status = $msg_header; }
922 # known_clients
923 $sql_statement = "SELECT * FROM known_clients WHERE hostname='$address'";
924 $res = $known_clients_db->select_dbentry($sql_statement);
925 if( keys(%$res) > 0) {
926 $act_status = $res->{1}->{'status'};
927 if( $act_status eq "down" ) {
928 $sql_statement = "DELETE FROM known_clients WHERE hostname='$address'";
929 $res = $known_clients_db->del_dbentry($sql_statement);
930 daemon_log("$session_id WARNING: failed 2x to send msg to host '$address', delete host from known_clients", 3);
931 } else {
932 $sql_statement = "UPDATE known_clients SET status='$new_status' WHERE hostname='$address'";
933 $res = $known_clients_db->update_dbentry($sql_statement);
934 if($new_status eq "down"){
935 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
936 } else {
937 daemon_log("$session_id INFO: set '$address' from status '$act_status' to '$new_status'", 5);
938 }
939 }
940 }
942 # known_server
943 $sql_statement = "SELECT * FROM known_server WHERE hostname='$address'";
944 $res = $known_server_db->select_dbentry($sql_statement);
945 if( keys(%$res) > 0 ) {
946 $act_status = $res->{1}->{'status'};
947 if( $act_status eq "down" ) {
948 $sql_statement = "DELETE FROM known_server WHERE hostname='$address'";
949 $res = $known_server_db->del_dbentry($sql_statement);
950 daemon_log("$session_id WARNING: failed 2x to a send msg to host '$address', delete host from known_server", 3);
951 }
952 else {
953 $sql_statement = "UPDATE known_server SET status='$new_status' WHERE hostname='$address'";
954 $res = $known_server_db->update_dbentry($sql_statement);
955 if($new_status eq "down"){
956 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
957 }
958 else {
959 daemon_log("$session_id INFO: set '$address' from status '$act_status' to '$new_status'", 5);
960 }
961 }
962 }
963 return $error;
964 }
967 sub update_jobdb_status_for_send_msgs {
968 my ($answer, $error) = @_;
969 if( $answer =~ /<jobdb_id>(\d+)<\/jobdb_id>/ ) {
970 my $jobdb_id = $1;
972 # sending msg faild
973 if( $error ) {
974 if (not $answer =~ /<header>trigger_action_reinstall<\/header>/) {
975 my $sql_statement = "UPDATE $job_queue_tn ".
976 "SET status='error', result='can not deliver msg, please consult log file' ".
977 "WHERE id=$jobdb_id";
978 my $res = $job_db->update_dbentry($sql_statement);
979 }
981 # sending msg was successful
982 } else {
983 my $sql_statement = "UPDATE $job_queue_tn ".
984 "SET status='done' ".
985 "WHERE id=$jobdb_id AND status='processed'";
986 my $res = $job_db->update_dbentry($sql_statement);
987 }
988 }
989 }
991 sub _start {
992 my ($kernel) = $_[KERNEL];
993 &trigger_db_loop($kernel);
994 $global_kernel = $kernel;
995 $kernel->yield('create_fai_server_db', $fai_server_tn );
996 $kernel->yield('create_fai_release_db', $fai_release_tn );
997 $kernel->sig(USR1 => "sig_handler");
998 $kernel->sig(USR2 => "create_packages_list_db");
999 }
1001 sub sig_handler {
1002 my ($kernel, $signal) = @_[KERNEL, ARG0] ;
1003 daemon_log("0 INFO got signal '$signal'", 1);
1004 $kernel->sig_handled();
1005 return;
1006 }
1009 sub msg_to_decrypt {
1010 my ($session, $heap) = @_[SESSION, HEAP];
1011 my $session_id = $session->ID;
1012 my ($msg, $msg_hash, $module);
1013 my $error = 0;
1015 # hole neue msg aus @msgs_to_decrypt
1016 my $next_msg = shift @msgs_to_decrypt;
1018 # entschlüssle sie
1020 # msg is from a new client or gosa
1021 ($msg, $msg_hash, $module) = &input_from_unknown_host($next_msg, $session_id);
1022 # msg is from a gosa-si-server or gosa-si-bus
1023 if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1024 ($msg, $msg_hash, $module) = &input_from_known_server($next_msg, $heap->{'remote_ip'}, $session_id);
1025 }
1026 # msg is from a gosa-si-client
1027 if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1028 ($msg, $msg_hash, $module) = &input_from_known_client($next_msg, $heap->{'remote_ip'}, $session_id);
1029 }
1030 # an error occurred
1031 if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1032 # if an incoming msg could not be decrypted (maybe a wrong key), send client a ping. If the client
1033 # could not understand a msg from its server the client cause a re-registering process
1034 daemon_log("$session_id INFO cannot understand incoming msg, send 'ping'-msg to all host with ip '".$heap->{remote_ip}."' to cause a re-registering of the client if necessary", 5);
1035 my $sql_statement = "SELECT * FROM $main::known_clients_tn WHERE (hostname LIKE '".$heap->{'remote_ip'}."%')";
1036 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1037 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1038 my $host_name = $hit->{'hostname'};
1039 my $host_key = $hit->{'hostkey'};
1040 my $ping_msg = "<xml> <header>gosa_ping</header> <source>$server_address</source> <target>$host_name</target></xml>";
1041 my $error = &send_msg_to_target($ping_msg, $host_name, $host_key, "gosa_ping", $session_id);
1042 &update_jobdb_status_for_send_msgs($ping_msg, $error);
1043 }
1044 $error++;
1045 }
1047 # add message to incoming_db
1048 if( $error == 0) {
1049 my $header = @{$msg_hash->{'header'}}[0];
1050 my $target = @{$msg_hash->{'target'}}[0];
1051 my $res = $incoming_db->add_dbentry( {table=>$incoming_tn,
1052 primkey=>[],
1053 headertag=>$header,
1054 targettag=>$target,
1055 xmlmessage=>$msg,
1056 timestamp=>&get_time,
1057 module=>$module,
1058 } );
1059 if ($res != 0) {
1060 # TODO ist das mit $! so ok???
1061 #&daemon_log("$session_id ERROR: cannot add message to incoming.db: $!", 1);
1062 }
1063 }
1065 }
1068 sub next_task {
1069 my ($session, $heap) = @_[SESSION, HEAP];
1070 # if (keys( %{ $heap->{task} } ) < $max_children ) {
1071 my $task = POE::Wheel::Run->new(
1072 Program => sub { process_task($session, $heap) },
1073 StdioFilter => POE::Filter::Reference->new(),
1074 StdoutEvent => "task_result",
1075 StderrEvent => "task_debug",
1076 CloseEvent => "task_done",
1077 );
1079 $heap->{task}->{ $task->ID } = $task;
1080 # }
1081 }
1083 sub handle_task_result {
1084 my ($kernel, $heap, $result) = @_[KERNEL, HEAP, ARG0];
1085 my $client_answer = $result->{'answer'};
1086 if( $client_answer =~ s/session_id=(\d+)$// ) {
1087 my $session_id = $1;
1088 if( defined $session_id ) {
1089 my $session_reference = $kernel->ID_id_to_session($session_id);
1090 if( defined $session_reference ) {
1091 $heap = $session_reference->get_heap();
1092 }
1093 }
1095 if(exists $heap->{'client'}) {
1096 $heap->{'client'}->put($client_answer);
1097 }
1098 }
1099 $kernel->sig(CHLD => "child_reap");
1100 }
1102 sub handle_task_debug {
1103 my $result = $_[ARG0];
1104 print STDERR "$result\n";
1105 }
1107 sub handle_task_done {
1108 my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
1109 delete $heap->{task}->{$task_id};
1110 }
1112 sub process_task {
1113 no strict "refs";
1114 my ($session, $heap, $input) = @_;
1115 my $session_id = $session->ID;
1116 my $error = 0;
1117 my $answer_l;
1118 my ($answer_header, @answer_target_l, $answer_source);
1119 my $client_answer = "";
1121 daemon_log("", 5);
1122 daemon_log("$session_id INFO: Incoming msg with session ID $session_id from '".$heap->{'remote_ip'}."'", 5);
1123 #daemon_log("$session_id DEBUG: Incoming msg:\n$input", 9);
1125 ##################################################
1126 # fetch first unprocessed message from incoming_db
1127 # sometimes the program is faster than sqlite, so wait until informations are present at db
1128 my $id_sql;
1129 my $id_res;
1130 my $message_id;
1131 # TODO : das hier ist sehr sehr unschön
1132 while (1) {
1133 $id_sql = "SELECT min(id) FROM $incoming_tn WHERE (NOT headertag LIKE 'answer%')";
1134 $id_res = $incoming_db->exec_statement($id_sql);
1135 $message_id = @{@$id_res[0]}[0];
1136 if (defined $message_id) { last }
1137 }
1139 # fetch new message from incoming_db
1140 my $sql = "SELECT * FROM $incoming_tn WHERE id=$message_id";
1141 my $res = $incoming_db->exec_statement($sql);
1143 # prepare all variables needed to process message
1144 my $msg = @{@$res[0]}[3];
1145 my $incoming_id = @{@$res[0]}[0];
1146 my $module = @{@$res[0]}[4];
1147 my $header = @{@$res[0]}[2];
1148 my $msg_hash = $xml->XMLin($msg, ForceArray=>1);
1150 # messages which are an answer to a still running process should not be processed here
1151 if ($header =~ /^answer_(\d+)/) {
1152 return;
1153 }
1155 # delete message from db
1156 my $delete_sql = "DELETE FROM $incoming_tn WHERE id=$incoming_id";
1157 my $delete_res = $incoming_db->exec_statement($delete_sql);
1159 ######################
1160 # process incoming msg
1161 if( $error == 0) {
1162 daemon_log("$session_id INFO: Incoming msg with header '".@{$msg_hash->{'header'}}[0].
1163 "' from '".$heap->{'remote_ip'}."'", 5);
1164 daemon_log("$session_id DEBUG: Processing module ".$module, 7);
1165 $answer_l = &{ $module."::process_incoming_msg" }($msg, $msg_hash, $session_id);
1167 if ( 0 < @{$answer_l} ) {
1168 my $answer_str = join("\n", @{$answer_l});
1169 daemon_log("$session_id DEBUG: $module: Got answer from module: \n".$answer_str,8);
1170 } else {
1171 daemon_log("$session_id DEBUG: $module: Got no answer from module!" ,8);
1172 }
1174 }
1175 if( !$answer_l ) { $error++ };
1177 ########
1178 # answer
1179 if( $error == 0 ) {
1181 foreach my $answer ( @{$answer_l} ) {
1182 # check outgoing msg to xml validity
1183 my $answer_hash = &check_outgoing_xml_validity($answer);
1184 if( not defined $answer_hash ) {
1185 next;
1186 }
1188 $answer_header = @{$answer_hash->{'header'}}[0];
1189 @answer_target_l = @{$answer_hash->{'target'}};
1190 $answer_source = @{$answer_hash->{'source'}}[0];
1192 # deliver msg to all targets
1193 foreach my $answer_target ( @answer_target_l ) {
1195 # targets of msg are all gosa-si-clients in known_clients_db
1196 if( $answer_target eq "*" ) {
1197 # answer is for all clients
1198 my $sql_statement= "SELECT * FROM known_clients";
1199 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1200 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1201 my $host_name = $hit->{hostname};
1202 my $host_key = $hit->{hostkey};
1203 my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1204 &update_jobdb_status_for_send_msgs($answer, $error);
1205 }
1206 }
1208 # targets of msg are all gosa-si-server in known_server_db
1209 elsif( $answer_target eq "KNOWN_SERVER" ) {
1210 # answer is for all server in known_server
1211 my $sql_statement= "SELECT * FROM known_server";
1212 my $query_res = $known_server_db->select_dbentry( $sql_statement );
1213 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1214 my $host_name = $hit->{hostname};
1215 my $host_key = $hit->{hostkey};
1216 $answer =~ s/KNOWN_SERVER/$host_name/g;
1217 my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1218 &update_jobdb_status_for_send_msgs($answer, $error);
1219 }
1220 }
1222 # target of msg is GOsa
1223 elsif( $answer_target eq "GOSA" ) {
1224 my $session_id = ($1) if $answer =~ /<session_id>(\d+?)<\/session_id>/;
1225 my $add_on = "";
1226 if( defined $session_id ) {
1227 $add_on = ".session_id=$session_id";
1228 }
1229 # answer is for GOSA and has to returned to connected client
1230 my $gosa_answer = &encrypt_msg($answer, $GosaPackages_key);
1231 $client_answer = $gosa_answer.$add_on;
1232 }
1234 # target of msg is job queue at this host
1235 elsif( $answer_target eq "JOBDB") {
1236 $answer =~ /<header>(\S+)<\/header>/;
1237 my $header;
1238 if( defined $1 ) { $header = $1; }
1239 my $error = &send_msg_to_target($answer, $server_address, $GosaPackages_key, $header, $session_id);
1240 &update_jobdb_status_for_send_msgs($answer, $error);
1241 }
1243 # target of msg is a mac address
1244 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 ) {
1245 daemon_log("$session_id INFO: target is mac address '$answer_target', looking for host in known_clients", 5);
1246 my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$answer_target'";
1247 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1248 my $found_ip_flag = 0;
1249 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1250 my $host_name = $hit->{hostname};
1251 my $host_key = $hit->{hostkey};
1252 $answer =~ s/$answer_target/$host_name/g;
1253 daemon_log("$session_id INFO: found host '$host_name', associated to '$answer_target'", 5);
1254 my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1255 &update_jobdb_status_for_send_msgs($answer, $error);
1256 $found_ip_flag++ ;
1257 }
1258 if( $found_ip_flag == 0) {
1259 daemon_log("$session_id WARNING: no host found in known_clients with mac address '$answer_target'", 3);
1260 if( $bus_activ eq "true" ) {
1261 daemon_log("$session_id INFO: try to forward msg '$answer_header' to bus '$bus_address'", 5);
1262 my $sql_statement = "SELECT * FROM known_server WHERE hostname='$bus_address'";
1263 my $query_res = $known_server_db->select_dbentry( $sql_statement );
1264 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1265 my $bus_address = $hit->{hostname};
1266 my $bus_key = $hit->{hostkey};
1267 my $error = &send_msg_to_target($answer, $bus_address, $bus_key, $answer_header, $session_id);
1268 &update_jobdb_status_for_send_msgs($answer, $error);
1269 last;
1270 }
1271 }
1273 }
1275 # answer is for one specific host
1276 } else {
1277 # get encrypt_key
1278 my $encrypt_key = &get_encrypt_key($answer_target);
1279 if( not defined $encrypt_key ) {
1280 # unknown target, forward msg to bus
1281 daemon_log("$session_id WARNING: unknown target '$answer_target'", 3);
1282 if( $bus_activ eq "true" ) {
1283 daemon_log("$session_id INFO: try to forward msg '$answer_header' to bus '$bus_address'", 5);
1284 my $sql_statement = "SELECT * FROM known_server WHERE hostname='$bus_address'";
1285 my $query_res = $known_server_db->select_dbentry( $sql_statement );
1286 my $res_length = keys( %{$query_res} );
1287 if( $res_length == 0 ){
1288 daemon_log("$session_id WARNING: send '$answer_header' to '$bus_address' failed, ".
1289 "no bus found in known_server", 3);
1290 }
1291 else {
1292 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1293 my $bus_key = $hit->{hostkey};
1294 my $error = &send_msg_to_target($answer, $bus_address, $bus_key, $answer_header,$session_id );
1295 &update_jobdb_status_for_send_msgs($answer, $error);
1296 }
1297 }
1298 }
1299 next;
1300 }
1301 my $error = &send_msg_to_target($answer, $answer_target, $encrypt_key, $answer_header,$session_id);
1302 &update_jobdb_status_for_send_msgs($answer, $error);
1303 }
1304 }
1305 }
1306 }
1308 my $filter = POE::Filter::Reference->new();
1309 my %result = (
1310 status => "seems ok to me",
1311 answer => $client_answer,
1312 );
1314 my $output = $filter->put( [ \%result ] );
1315 print @$output;
1318 }
1321 sub trigger_db_loop {
1322 my ($kernel) = @_ ;
1323 $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1324 $kernel->delay_set('watch_for_done_jobs', $job_queue_loop_delay);
1325 $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay);
1326 $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay);
1327 $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay);
1328 }
1331 sub watch_for_done_jobs {
1332 my ($kernel,$heap) = @_[KERNEL, HEAP];
1334 my $sql_statement = "SELECT * FROM ".$job_queue_tn.
1335 " WHERE status='done'";
1336 my $res = $job_db->select_dbentry( $sql_statement );
1338 while( my ($id, $hit) = each %{$res} ) {
1339 my $jobdb_id = $hit->{id};
1340 my $sql_statement = "DELETE FROM $job_queue_tn WHERE id=$jobdb_id";
1341 my $res = $job_db->del_dbentry($sql_statement);
1342 }
1344 $kernel->delay_set('watch_for_done_jobs',$job_queue_loop_delay);
1345 }
1348 sub watch_for_new_jobs {
1349 if($watch_for_new_jobs_in_progress == 0) {
1350 $watch_for_new_jobs_in_progress = 1;
1351 my ($kernel,$heap) = @_[KERNEL, HEAP];
1353 # check gosa job queue for jobs with executable timestamp
1354 my $timestamp = &get_time();
1355 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE status='waiting' AND (CAST (timestamp AS INTEGER)) < $timestamp ORDER BY timestamp";
1356 my $res = $job_db->exec_statement( $sql_statement );
1358 # Merge all new jobs that would do the same actions
1359 my @drops;
1360 my $hits;
1361 foreach my $hit (reverse @{$res} ) {
1362 my $macaddress= lc @{$hit}[8];
1363 my $headertag= @{$hit}[5];
1364 if(
1365 defined($hits->{$macaddress}) &&
1366 defined($hits->{$macaddress}->{$headertag}) &&
1367 defined($hits->{$macaddress}->{$headertag}[0])
1368 ) {
1369 push @drops, "DELETE FROM $job_queue_tn WHERE id = $hits->{$macaddress}->{$headertag}[0]";
1370 }
1371 $hits->{$macaddress}->{$headertag}= $hit;
1372 }
1374 # Delete new jobs with a matching job in state 'processing'
1375 foreach my $macaddress (keys %{$hits}) {
1376 foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1377 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1378 if(defined($jobdb_id)) {
1379 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND headertag='$jobdb_headertag' AND status='processing'";
1380 my $res = $job_db->exec_statement( $sql_statement );
1381 foreach my $hit (@{$res}) {
1382 push @drops, "DELETE FROM $job_queue_tn WHERE id=$jobdb_id";
1383 }
1384 } else {
1385 daemon_log("J ERROR: Job without id exists for macaddress $macaddress!", 1);
1386 }
1387 }
1388 }
1390 # Commit deletion
1391 $job_db->exec_statementlist(\@drops);
1393 # Look for new jobs that could be executed
1394 foreach my $macaddress (keys %{$hits}) {
1396 # Look if there is an executing job
1397 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND status='processing'";
1398 my $res = $job_db->exec_statement( $sql_statement );
1400 # Skip new jobs for host if there is a processing job
1401 if(defined($res) and defined @{$res}[0]) {
1402 next;
1403 }
1405 foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1406 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1407 if(defined($jobdb_id)) {
1408 my $job_msg = @{$hits->{$macaddress}->{$jobdb_headertag}}[7];
1410 daemon_log("J DEBUG: its time to execute $job_msg", 7);
1411 my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$macaddress'";
1412 my $res_hash = $known_clients_db->select_dbentry( $sql_statement );
1414 # expect macaddress is unique!!!!!!
1415 my $target = $res_hash->{1}->{hostname};
1417 # change header
1418 $job_msg =~ s/<header>job_/<header>gosa_/;
1420 # add sqlite_id
1421 $job_msg =~ s/<\/xml>$/<jobdb_id>$jobdb_id<\/jobdb_id><\/xml>/;
1423 $job_msg =~ /<header>(\S+)<\/header>/;
1424 my $header = $1 ;
1425 my $func_error = &send_msg_to_target($job_msg, $server_address, $GosaPackages_key, $header, "J");
1427 # update status in job queue to 'processing'
1428 $sql_statement = "UPDATE $job_queue_tn SET status='processing' WHERE id=$jobdb_id";
1429 my $res = $job_db->update_dbentry($sql_statement);
1430 # TODO: abfangen ob alles in ordnung ist oder nicht, wenn nicht error schmeißen
1432 # We don't want parallel processing
1433 last;
1434 }
1435 }
1436 }
1438 $watch_for_new_jobs_in_progress = 0;
1439 $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1440 }
1441 }
1444 sub watch_for_new_messages {
1445 my ($kernel,$heap) = @_[KERNEL, HEAP];
1446 my @coll_user_msg; # collection list of outgoing messages
1448 # check messaging_db for new incoming messages with executable timestamp
1449 my $timestamp = &get_time();
1450 my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( (CAST(timestamp AS INTEGER))<$timestamp AND flag='n' AND direction='in' )";
1451 my $res = $messaging_db->exec_statement( $sql_statement );
1452 foreach my $hit (@{$res}) {
1454 # create outgoing messages
1455 my $message_to = @{$hit}[3];
1456 # translate message_to to plain login name
1457 my @message_to_l = split(/,/, $message_to);
1458 my %receiver_h;
1459 foreach my $receiver (@message_to_l) {
1460 if ($receiver =~ /^u_([\s\S]*)$/) {
1461 $receiver_h{$1} = 0;
1462 } elsif ($receiver =~ /^g_([\s\S]*)$/) {
1463 # TODO implement receiver translation
1464 # fetch all group members from ldap
1465 my $ldap_handle = &get_ldap_handle($session_id);
1466 my $mesg = $ldap_handle->search(
1467 base => $ldap_base,
1468 scope => 'sub',
1469 attrs => ['memberUid'],
1470 filter => "cn=$1";
1471 );
1472 if ($mesg->count) {
1473 my @entries = $mesg->entries;
1474 foreach my $entry (@entries) {
1475 my @receivers= $entry->get_value("memberUid");
1476 foreach my $receiver (@receivers) {
1477 $receiver_h{$1} = 0;
1478 }
1479 }
1480 }
1481 # if ($mesg->count) {
1482 # my @entries = $mesg->entries;
1483 # foreach my $entry (@entries) {
1484 # # Only modify entry if it is not set to '$state'
1485 # if ($entry->get_value("FAIstate") ne "$state"){
1486 # daemon_log("$session_id INFO: Setting FAIstate to '$state' for ".$entry->dn, 5);
1487 # my $result;
1488 # my %tmp = map { $_ => 1 } $entry->get_value("objectClass");
1489 # if (exists $tmp{'FAIobject'}){
1490 # if ($state eq ''){
1491 # $result= $ldap_handle->modify($entry->dn, changes => [
1492 # delete => [ FAIstate => [] ] ]);
1493 # } else {
1494 # $result= $ldap_handle->modify($entry->dn, changes => [
1495 # replace => [ FAIstate => $state ] ]);
1496 # }
1497 # } elsif ($state ne ''){
1498 # $result= $ldap_handle->modify($entry->dn, changes => [
1499 # add => [ objectClass => 'FAIobject' ],
1500 # add => [ FAIstate => $state ] ]);
1501 # }
1502 #
1503 # # Errors?
1504 # if ($result->code){
1505 # daemon_log("$session_id Error: Setting FAIstate to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
1506 # }
1507 # } else {
1508 # daemon_log("$session_id DEBUG FAIstate at host '".$entry->dn."' already at state '$st'", 7);
1509 # }
1510 # }
1511 # }
1512 # # if no ldap handle defined
1513 # } else {
1514 # daemon_log("$session_id ERROR: no LDAP handle defined for update FAIstate", 1);
1515 # }
1516 #
1518 # add each member to receiver hash
1520 } else {
1521 my $sbjct = &encode_base64(@{$hit}[1]);
1522 my $msg = &encode_base64(@{$hit}[7]);
1523 &daemon_log("M WARNING: unknown receiver '$receiver' for a user-message 'sbjct - msg'", 3);
1524 }
1525 }
1526 my @receiver_l = keys(%receiver_h);
1528 my $message_id = @{$hit}[0];
1530 #add each outgoing msg to messaging_db
1531 my $receiver;
1532 foreach $receiver (@receiver_l) {
1533 my $sql_statement = "INSERT INTO $messaging_tn (id, subject, message_from, message_to, flag, direction, delivery_time, message, timestamp) ".
1534 "VALUES ('".
1535 $message_id."', '". # id
1536 @{$hit}[1]."', '". # subject
1537 @{$hit}[2]."', '". # message_from
1538 $receiver."', '". # message_to
1539 "none"."', '". # flag
1540 "out"."', '". # direction
1541 @{$hit}[6]."', '". # delivery_time
1542 @{$hit}[7]."', '". # message
1543 $timestamp."'". # timestamp
1544 ")";
1545 &daemon_log("M DEBUG: $sql_statement", 1);
1546 my $res = $messaging_db->exec_statement($sql_statement);
1547 &daemon_log("M INFO: message '".@{$hit}[0]."' is prepared for delivery to receiver '$receiver'", 5);
1548 }
1550 # set incoming message to flag d=deliverd
1551 $sql_statement = "UPDATE $messaging_tn SET flag='p' WHERE id='$message_id'";
1552 &daemon_log("M DEBUG: $sql_statement", 7);
1553 $res = $messaging_db->update_dbentry($sql_statement);
1554 &daemon_log("M INFO: message '$message_id' is set to flag 'p' (processed)", 5);
1555 }
1557 $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay);
1558 return;
1559 }
1561 sub watch_for_delivery_messages {
1562 my ($kernel, $heap) = @_[KERNEL, HEAP];
1564 # select outgoing messages
1565 my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( flag='p' AND direction='out' )";
1566 #&daemon_log("0 DEBUG: $sql", 7);
1567 my $res = $messaging_db->exec_statement( $sql_statement );
1569 # build out msg for each usr
1570 foreach my $hit (@{$res}) {
1571 my $receiver = @{$hit}[3];
1572 my $msg_id = @{$hit}[0];
1573 my $subject = @{$hit}[1];
1574 my $message = @{$hit}[7];
1576 # resolve usr -> host where usr is logged in
1577 my $sql = "SELECT * FROM $login_users_tn WHERE (user='$receiver')";
1578 #&daemon_log("0 DEBUG: $sql", 7);
1579 my $res = $login_users_db->exec_statement($sql);
1581 # reciver is logged in nowhere
1582 if (not ref(@$res[0]) eq "ARRAY") { next; }
1584 my $send_succeed = 0;
1585 foreach my $hit (@$res) {
1586 my $receiver_host = @$hit[0];
1587 &daemon_log("M DEBUG: user '$receiver' is logged in at host '$receiver_host'", 7);
1589 # fetch key to encrypt msg propperly for usr/host
1590 my $sql = "SELECT * FROM $known_clients_tn WHERE (hostname='$receiver_host')";
1591 &daemon_log("0 DEBUG: $sql", 7);
1592 my $res = $known_clients_db->exec_statement($sql);
1594 # host is already down
1595 if (not ref(@$res[0]) eq "ARRAY") { next; }
1597 # host is on
1598 my $receiver_key = @{@{$res}[0]}[2];
1599 my %data = ('subject' => $subject, 'message' => $message, 'usr' => $receiver);
1600 my $out_msg = &build_msg("usr_msg", $server_address, $receiver_host, \%data );
1601 my $error = &send_msg_to_target($out_msg, $receiver_host, $receiver_key, "usr_msg", 0);
1602 if ($error == 0 ) {
1603 $send_succeed++ ;
1604 }
1605 }
1607 if ($send_succeed) {
1608 # set outgoing msg at db to deliverd
1609 my $sql = "UPDATE $messaging_tn SET flag='d' WHERE (id='$msg_id' AND direction='out' AND message_to='$receiver')";
1610 &daemon_log("0 DEBUG: $sql", 7);
1611 my $res = $messaging_db->exec_statement($sql);
1612 }
1613 }
1615 $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay);
1616 return;
1617 }
1620 sub watch_for_done_messages {
1621 my ($kernel,$heap) = @_[KERNEL, HEAP];
1623 my $sql = "SELECT * FROM $messaging_tn WHERE (flag='p' AND direction='in')";
1624 #&daemon_log("0 DEBUG: $sql", 7);
1625 my $res = $messaging_db->exec_statement($sql);
1627 foreach my $hit (@{$res}) {
1628 my $msg_id = @{$hit}[0];
1630 my $sql = "SELECT * FROM $messaging_tn WHERE (id='$msg_id' AND direction='out' AND (NOT flag='s'))";
1631 #&daemon_log("0 DEBUG: $sql", 7);
1632 my $res = $messaging_db->exec_statement($sql);
1634 # not all usr msgs have been seen till now
1635 if ( ref(@$res[0]) eq "ARRAY") { next; }
1637 $sql = "DELETE FROM $messaging_tn WHERE (id='$msg_id')";
1638 #&daemon_log("0 DEBUG: $sql", 7);
1639 $res = $messaging_db->exec_statement($sql);
1641 }
1643 $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay);
1644 return;
1645 }
1648 sub get_ldap_handle {
1649 my ($session_id) = @_;
1650 my $heap;
1651 my $ldap_handle;
1653 if (not defined $session_id ) { $session_id = 0 };
1654 if ($session_id =~ /[^0-9]*/) { $session_id = 0 };
1656 if ($session_id == 0) {
1657 daemon_log("$session_id DEBUG: get_ldap_handle invoked without a session_id, create a new ldap_handle", 7);
1658 $ldap_handle = Net::LDAP->new( $ldap_uri );
1659 $ldap_handle->bind($ldap_admin_dn, password => $ldap_admin_password);
1661 } else {
1662 my $session_reference = $global_kernel->ID_id_to_session($session_id);
1663 if( defined $session_reference ) {
1664 $heap = $session_reference->get_heap();
1665 }
1667 if (not defined $heap) {
1668 daemon_log("$session_id DEBUG: cannot get heap for session_id '$session_id'", 7);
1669 return;
1670 }
1672 # TODO: This "if" is nonsense, because it doesn't prove that the
1673 # used handle is still valid - or if we've to reconnect...
1674 #if (not exists $heap->{ldap_handle}) {
1675 $ldap_handle = Net::LDAP->new( $ldap_uri );
1676 $ldap_handle->bind($ldap_admin_dn, password => $ldap_admin_password);
1677 $heap->{ldap_handle} = $ldap_handle;
1678 #}
1679 }
1680 return $ldap_handle;
1681 }
1684 sub change_fai_state {
1685 my ($st, $targets, $session_id) = @_;
1686 $session_id = 0 if not defined $session_id;
1687 # Set FAI state to localboot
1688 my %mapActions= (
1689 reboot => '',
1690 update => 'softupdate',
1691 localboot => 'localboot',
1692 reinstall => 'install',
1693 rescan => '',
1694 wake => '',
1695 memcheck => 'memcheck',
1696 sysinfo => 'sysinfo',
1697 install => 'install',
1698 );
1700 # Return if this is unknown
1701 if (!exists $mapActions{ $st }){
1702 daemon_log("$session_id ERROR: unknown action '$st', can not translate ot FAIstate", 1);
1703 return;
1704 }
1706 my $state= $mapActions{ $st };
1708 my $ldap_handle = &get_ldap_handle($session_id);
1709 if( defined($ldap_handle) ) {
1711 # Build search filter for hosts
1712 my $search= "(&(objectClass=GOhard)";
1713 foreach (@{$targets}){
1714 $search.= "(macAddress=$_)";
1715 }
1716 $search.= ")";
1718 # If there's any host inside of the search string, procress them
1719 if (!($search =~ /macAddress/)){
1720 daemon_log("$session_id ERROR: no macAddress found in filter statement for LDAP search: '$search'", 1);
1721 return;
1722 }
1724 # Perform search for Unit Tag
1725 my $mesg = $ldap_handle->search(
1726 base => $ldap_base,
1727 scope => 'sub',
1728 attrs => ['dn', 'FAIstate', 'objectClass'],
1729 filter => "$search"
1730 );
1732 if ($mesg->count) {
1733 my @entries = $mesg->entries;
1734 foreach my $entry (@entries) {
1735 # Only modify entry if it is not set to '$state'
1736 if ($entry->get_value("FAIstate") ne "$state"){
1737 daemon_log("$session_id INFO: Setting FAIstate to '$state' for ".$entry->dn, 5);
1738 my $result;
1739 my %tmp = map { $_ => 1 } $entry->get_value("objectClass");
1740 if (exists $tmp{'FAIobject'}){
1741 if ($state eq ''){
1742 $result= $ldap_handle->modify($entry->dn, changes => [
1743 delete => [ FAIstate => [] ] ]);
1744 } else {
1745 $result= $ldap_handle->modify($entry->dn, changes => [
1746 replace => [ FAIstate => $state ] ]);
1747 }
1748 } elsif ($state ne ''){
1749 $result= $ldap_handle->modify($entry->dn, changes => [
1750 add => [ objectClass => 'FAIobject' ],
1751 add => [ FAIstate => $state ] ]);
1752 }
1754 # Errors?
1755 if ($result->code){
1756 daemon_log("$session_id Error: Setting FAIstate to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
1757 }
1758 } else {
1759 daemon_log("$session_id DEBUG FAIstate at host '".$entry->dn."' already at state '$st'", 7);
1760 }
1761 }
1762 }
1763 # if no ldap handle defined
1764 } else {
1765 daemon_log("$session_id ERROR: no LDAP handle defined for update FAIstate", 1);
1766 }
1768 }
1771 sub change_goto_state {
1772 my ($st, $targets, $session_id) = @_;
1773 $session_id = 0 if not defined $session_id;
1775 # Switch on or off?
1776 my $state= $st eq 'active' ? 'active': 'locked';
1778 my $ldap_handle = &get_ldap_handle($session_id);
1779 if( defined($ldap_handle) ) {
1781 # Build search filter for hosts
1782 my $search= "(&(objectClass=GOhard)";
1783 foreach (@{$targets}){
1784 $search.= "(macAddress=$_)";
1785 }
1786 $search.= ")";
1788 # If there's any host inside of the search string, procress them
1789 if (!($search =~ /macAddress/)){
1790 return;
1791 }
1793 # Perform search for Unit Tag
1794 my $mesg = $ldap_handle->search(
1795 base => $ldap_base,
1796 scope => 'sub',
1797 attrs => ['dn', 'gotoMode'],
1798 filter => "$search"
1799 );
1801 if ($mesg->count) {
1802 my @entries = $mesg->entries;
1803 foreach my $entry (@entries) {
1805 # Only modify entry if it is not set to '$state'
1806 if ($entry->get_value("gotoMode") ne $state){
1808 daemon_log("$session_id INFO: Setting gotoMode to '$state' for ".$entry->dn, 5);
1809 my $result;
1810 $result= $ldap_handle->modify($entry->dn, changes => [
1811 replace => [ gotoMode => $state ] ]);
1813 # Errors?
1814 if ($result->code){
1815 &daemon_log("$session_id Error: Setting gotoMode to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
1816 }
1818 }
1819 }
1820 }
1822 }
1823 }
1826 sub run_create_fai_server_db {
1827 my ($kernel, $session, $heap, $table_name) = @_[KERNEL, SESSION, HEAP, ARG0];
1828 my $session_id = $session->ID;
1829 my $task = POE::Wheel::Run->new(
1830 Program => sub { &create_fai_server_db($table_name,$kernel, undef, $session_id) },
1831 StdoutEvent => "session_run_result",
1832 StderrEvent => "session_run_debug",
1833 CloseEvent => "session_run_done",
1834 );
1836 $heap->{task}->{ $task->ID } = $task;
1837 return;
1838 }
1841 sub create_fai_server_db {
1842 my ($table_name, $kernel, $dont_create_packages_list, $session_id) = @_;
1843 my $result;
1845 if (not defined $session_id) { $session_id = 0; }
1846 my $ldap_handle = &get_ldap_handle();
1847 if(defined($ldap_handle)) {
1848 daemon_log("$session_id INFO: create_fai_server_db: start", 5);
1849 my $mesg= $ldap_handle->search(
1850 base => $ldap_base,
1851 scope => 'sub',
1852 attrs => ['FAIrepository', 'gosaUnitTag'],
1853 filter => "(&(FAIrepository=*)(objectClass=FAIrepositoryServer))",
1854 );
1855 if($mesg->{'resultCode'} == 0 &&
1856 $mesg->count != 0) {
1857 foreach my $entry (@{$mesg->{entries}}) {
1858 if($entry->exists('FAIrepository')) {
1859 # Add an entry for each Repository configured for server
1860 foreach my $repo(@{$entry->get_value('FAIrepository', asref => 1)}) {
1861 my($tmp_url,$tmp_server,$tmp_release,$tmp_sections) = split(/\|/, $repo);
1862 my $tmp_tag= $entry->get_value('gosaUnitTag') || "";
1863 $result= $fai_server_db->add_dbentry( {
1864 table => $table_name,
1865 primkey => ['server', 'release', 'tag'],
1866 server => $tmp_url,
1867 release => $tmp_release,
1868 sections => $tmp_sections,
1869 tag => (length($tmp_tag)>0)?$tmp_tag:"",
1870 } );
1871 }
1872 }
1873 }
1874 }
1875 daemon_log("$session_id INFO: create_fai_server_db: finished", 5);
1877 # TODO: Find a way to post the 'create_packages_list_db' event
1878 if(not defined($dont_create_packages_list)) {
1879 &create_packages_list_db(undef, undef, $session_id);
1880 }
1881 }
1883 $ldap_handle->disconnect;
1884 return $result;
1885 }
1888 sub run_create_fai_release_db {
1889 my ($session, $heap, $table_name) = @_[SESSION, HEAP, ARG0];
1890 my $session_id = $session->ID;
1891 my $task = POE::Wheel::Run->new(
1892 Program => sub { &create_fai_release_db($table_name, $session_id) },
1893 StdoutEvent => "session_run_result",
1894 StderrEvent => "session_run_debug",
1895 CloseEvent => "session_run_done",
1896 );
1898 $heap->{task}->{ $task->ID } = $task;
1899 return;
1900 }
1903 sub create_fai_release_db {
1904 my ($table_name, $session_id) = @_;
1905 my $result;
1907 # used for logging
1908 if (not defined $session_id) { $session_id = 0; }
1910 my $ldap_handle = &get_ldap_handle();
1911 if(defined($ldap_handle)) {
1912 daemon_log("$session_id INFO: create_fai_release_db: start",5);
1913 my $mesg= $ldap_handle->search(
1914 base => $ldap_base,
1915 scope => 'sub',
1916 attrs => [],
1917 filter => "(&(objectClass=organizationalUnit)(ou=fai))",
1918 );
1919 if($mesg->{'resultCode'} == 0 &&
1920 $mesg->count != 0) {
1921 # Walk through all possible FAI container ou's
1922 my @sql_list;
1923 my $timestamp= &get_time();
1924 foreach my $ou (@{$mesg->{entries}}) {
1925 my $tmp_classes= resolve_fai_classes($ou->dn, $ldap_handle, $session_id);
1926 if(defined($tmp_classes) && ref($tmp_classes) eq 'HASH') {
1927 my @tmp_array=get_fai_release_entries($tmp_classes);
1928 if(@tmp_array) {
1929 foreach my $entry (@tmp_array) {
1930 if(defined($entry) && ref($entry) eq 'HASH') {
1931 my $sql=
1932 "INSERT INTO $table_name "
1933 ."(timestamp, release, class, type, state) VALUES ("
1934 .$timestamp.","
1935 ."'".$entry->{'release'}."',"
1936 ."'".$entry->{'class'}."',"
1937 ."'".$entry->{'type'}."',"
1938 ."'".$entry->{'state'}."')";
1939 push @sql_list, $sql;
1940 }
1941 }
1942 }
1943 }
1944 }
1946 daemon_log("$session_id DEBUG: Inserting ".scalar @sql_list." entries to DB",8);
1947 if(@sql_list) {
1948 unshift @sql_list, "VACUUM";
1949 unshift @sql_list, "DELETE FROM $table_name";
1950 $fai_release_db->exec_statementlist(\@sql_list);
1951 }
1952 daemon_log("$session_id DEBUG: Done with inserting",7);
1953 }
1954 daemon_log("$session_id INFO: create_fai_release_db: finished",5);
1955 }
1956 $ldap_handle->disconnect;
1957 return $result;
1958 }
1960 sub get_fai_types {
1961 my $tmp_classes = shift || return undef;
1962 my @result;
1964 foreach my $type(keys %{$tmp_classes}) {
1965 if(defined($tmp_classes->{$type}[0]) && (!($tmp_classes->{$type}[0] =~ /^.*?removed.*?$/))) {
1966 my $entry = {
1967 type => $type,
1968 state => $tmp_classes->{$type}[0],
1969 };
1970 push @result, $entry;
1971 }
1972 }
1974 return @result;
1975 }
1977 sub get_fai_state {
1978 my $result = "";
1979 my $tmp_classes = shift || return $result;
1981 foreach my $type(keys %{$tmp_classes}) {
1982 if(defined($tmp_classes->{$type}[0])) {
1983 $result = $tmp_classes->{$type}[0];
1985 # State is equal for all types in class
1986 last;
1987 }
1988 }
1990 return $result;
1991 }
1993 sub resolve_fai_classes {
1994 my ($fai_base, $ldap_handle, $session_id) = @_;
1995 if (not defined $session_id) { $session_id = 0; }
1996 my $result;
1997 my @possible_fai_classes= ("FAIscript", "FAIhook", "FAIpartitionTable", "FAItemplate", "FAIvariable", "FAIprofile", "FAIpackageList");
1998 my $fai_filter= "(|(&(objectClass=FAIclass)(|(objectClass=".join(")(objectClass=", @possible_fai_classes).")))(objectClass=FAIbranch))";
1999 my $fai_classes;
2001 daemon_log("$session_id DEBUG: Searching for FAI entries in base $fai_base",7);
2002 my $mesg= $ldap_handle->search(
2003 base => $fai_base,
2004 scope => 'sub',
2005 attrs => ['cn','objectClass','FAIstate'],
2006 filter => $fai_filter,
2007 );
2008 daemon_log("$session_id DEBUG: Found ".$mesg->count()." FAI entries",7);
2010 if($mesg->{'resultCode'} == 0 &&
2011 $mesg->count != 0) {
2012 foreach my $entry (@{$mesg->{entries}}) {
2013 if($entry->exists('cn')) {
2014 my $tmp_dn= $entry->dn();
2016 # Skip classname and ou dn parts for class
2017 my $tmp_release = ($1) if $tmp_dn =~ /^[^,]+,[^,]+,(.*?),$fai_base$/;
2019 # Skip classes without releases
2020 if((!defined($tmp_release)) || length($tmp_release)==0) {
2021 next;
2022 }
2024 my $tmp_cn= $entry->get_value('cn');
2025 my $tmp_state= $entry->get_value('FAIstate');
2027 my $tmp_type;
2028 # Get FAI type
2029 for my $oclass(@{$entry->get_value('objectClass', asref => 1)}) {
2030 if(grep $_ eq $oclass, @possible_fai_classes) {
2031 $tmp_type= $oclass;
2032 last;
2033 }
2034 }
2036 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
2037 # A Subrelease
2038 my @sub_releases = split(/,/, $tmp_release);
2040 # Walk through subreleases and build hash tree
2041 my $hash;
2042 while(my $tmp_sub_release = pop @sub_releases) {
2043 $hash .= "\{'$tmp_sub_release'\}->";
2044 }
2045 eval('push @{$fai_classes->'.$hash.'{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";');
2046 } else {
2047 # A branch, no subrelease
2048 push @{$fai_classes->{$tmp_release}->{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";
2049 }
2050 } elsif (!$entry->exists('cn')) {
2051 my $tmp_dn= $entry->dn();
2052 my $tmp_release = ($1) if $tmp_dn =~ /^(.*?),$fai_base$/;
2054 # Skip classes without releases
2055 if((!defined($tmp_release)) || length($tmp_release)==0) {
2056 next;
2057 }
2059 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
2060 # A Subrelease
2061 my @sub_releases= split(/,/, $tmp_release);
2063 # Walk through subreleases and build hash tree
2064 my $hash;
2065 while(my $tmp_sub_release = pop @sub_releases) {
2066 $hash .= "\{'$tmp_sub_release'\}->";
2067 }
2068 # Remove the last two characters
2069 chop($hash);
2070 chop($hash);
2072 eval('$fai_classes->'.$hash.'= {}');
2073 } else {
2074 # A branch, no subrelease
2075 if(!exists($fai_classes->{$tmp_release})) {
2076 $fai_classes->{$tmp_release} = {};
2077 }
2078 }
2079 }
2080 }
2082 # The hash is complete, now we can honor the copy-on-write based missing entries
2083 foreach my $release (keys %$fai_classes) {
2084 $result->{$release}= deep_copy(apply_fai_inheritance($fai_classes->{$release}));
2085 }
2086 }
2087 return $result;
2088 }
2090 sub apply_fai_inheritance {
2091 my $fai_classes = shift || return {};
2092 my $tmp_classes;
2094 # Get the classes from the branch
2095 foreach my $class (keys %{$fai_classes}) {
2096 # Skip subreleases
2097 if($class =~ /^ou=.*$/) {
2098 next;
2099 } else {
2100 $tmp_classes->{$class}= deep_copy($fai_classes->{$class});
2101 }
2102 }
2104 # Apply to each subrelease
2105 foreach my $subrelease (keys %{$fai_classes}) {
2106 if($subrelease =~ /ou=/) {
2107 foreach my $tmp_class (keys %{$tmp_classes}) {
2108 if(!exists($fai_classes->{$subrelease}->{$tmp_class})) {
2109 $fai_classes->{$subrelease}->{$tmp_class} =
2110 deep_copy($tmp_classes->{$tmp_class});
2111 } else {
2112 foreach my $type (keys %{$tmp_classes->{$tmp_class}}) {
2113 if(!exists($fai_classes->{$subrelease}->{$tmp_class}->{$type})) {
2114 $fai_classes->{$subrelease}->{$tmp_class}->{$type}=
2115 deep_copy($tmp_classes->{$tmp_class}->{$type});
2116 }
2117 }
2118 }
2119 }
2120 }
2121 }
2123 # Find subreleases in deeper levels
2124 foreach my $subrelease (keys %{$fai_classes}) {
2125 if($subrelease =~ /ou=/) {
2126 foreach my $subsubrelease (keys %{$fai_classes->{$subrelease}}) {
2127 if($subsubrelease =~ /ou=/) {
2128 apply_fai_inheritance($fai_classes->{$subrelease});
2129 }
2130 }
2131 }
2132 }
2134 return $fai_classes;
2135 }
2137 sub get_fai_release_entries {
2138 my $tmp_classes = shift || return;
2139 my $parent = shift || "";
2140 my @result = shift || ();
2142 foreach my $entry (keys %{$tmp_classes}) {
2143 if(defined($entry)) {
2144 if($entry =~ /^ou=.*$/) {
2145 my $release_name = $entry;
2146 $release_name =~ s/ou=//g;
2147 if(length($parent)>0) {
2148 $release_name = $parent."/".$release_name;
2149 }
2150 my @bufentries = get_fai_release_entries($tmp_classes->{$entry}, $release_name, @result);
2151 foreach my $bufentry(@bufentries) {
2152 push @result, $bufentry;
2153 }
2154 } else {
2155 my @types = get_fai_types($tmp_classes->{$entry});
2156 foreach my $type (@types) {
2157 push @result,
2158 {
2159 'class' => $entry,
2160 'type' => $type->{'type'},
2161 'release' => $parent,
2162 'state' => $type->{'state'},
2163 };
2164 }
2165 }
2166 }
2167 }
2169 return @result;
2170 }
2172 sub deep_copy {
2173 my $this = shift;
2174 if (not ref $this) {
2175 $this;
2176 } elsif (ref $this eq "ARRAY") {
2177 [map deep_copy($_), @$this];
2178 } elsif (ref $this eq "HASH") {
2179 +{map { $_ => deep_copy($this->{$_}) } keys %$this};
2180 } else { die "what type is $_?" }
2181 }
2184 sub session_run_result {
2185 my ($kernel, $heap, $client_answer) = @_[KERNEL, HEAP, ARG0];
2186 $kernel->sig(CHLD => "child_reap");
2187 }
2189 sub session_run_debug {
2190 my $result = $_[ARG0];
2191 print STDERR "$result\n";
2192 }
2194 sub session_run_done {
2195 my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
2196 delete $heap->{task}->{$task_id};
2197 }
2200 sub create_sources_list {
2201 my $session_id = shift;
2202 my $ldap_handle = &main::get_ldap_handle;
2203 my $result="/tmp/gosa_si_tmp_sources_list";
2205 # Remove old file
2206 if(stat($result)) {
2207 unlink($result);
2208 &main::daemon_log("$session_id DEBUG: remove an old version of '$result'", 7);
2209 }
2211 my $fh;
2212 open($fh, ">$result");
2213 if (not defined $fh) {
2214 &main::daemon_log("$session_id DEBUG: cannot open '$result' for writing", 7);
2215 return undef;
2216 }
2217 if(defined($main::ldap_server_dn) and length($main::ldap_server_dn) > 0) {
2218 my $mesg=$ldap_handle->search(
2219 base => $main::ldap_server_dn,
2220 scope => 'base',
2221 attrs => 'FAIrepository',
2222 filter => 'objectClass=FAIrepositoryServer'
2223 );
2224 if($mesg->count) {
2225 foreach my $entry(@{$mesg->{'entries'}}) {
2226 foreach my $value(@{$entry->get_value('FAIrepository', asref => 1)}) {
2227 my ($server, $tag, $release, $sections)= split /\|/, $value;
2228 my $line = "deb $server $release";
2229 $sections =~ s/,/ /g;
2230 $line.= " $sections";
2231 print $fh $line."\n";
2232 }
2233 }
2234 }
2235 } else {
2236 if (defined $main::ldap_server_dn){
2237 &main::daemon_log("$session_id ERROR: something wrong with ldap_server_dn '$main::ldap_server_dn', abort create_sources_list", 1);
2238 } else {
2239 &main::daemon_log("$session_id ERROR: no ldap_server_dn found, abort create_sources_list", 1);
2240 }
2241 }
2242 close($fh);
2244 return $result;
2245 }
2248 sub run_create_packages_list_db {
2249 my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
2250 my $session_id = $session->ID;
2252 my $task = POE::Wheel::Run->new(
2253 Priority => +20,
2254 Program => sub {&create_packages_list_db(undef, undef, $session_id)},
2255 StdoutEvent => "session_run_result",
2256 StderrEvent => "session_run_debug",
2257 CloseEvent => "session_run_done",
2258 );
2259 $heap->{task}->{ $task->ID } = $task;
2260 }
2263 sub create_packages_list_db {
2264 my ($ldap_handle, $sources_file, $session_id) = @_;
2266 # it should not be possible to trigger a recreation of packages_list_db
2267 # while packages_list_db is under construction, so set flag packages_list_under_construction
2268 # which is tested befor recreation can be started
2269 if (-r $packages_list_under_construction) {
2270 daemon_log("$session_id WARNING: packages_list_db is right now under construction, please wait until this process is finished", 3);
2271 return;
2272 } else {
2273 daemon_log("$session_id INFO: create_packages_list_db: start", 5);
2274 # set packages_list_under_construction to true
2275 system("touch $packages_list_under_construction");
2276 @packages_list_statements=();
2277 }
2279 if (not defined $session_id) { $session_id = 0; }
2280 if (not defined $ldap_handle) {
2281 $ldap_handle= &get_ldap_handle();
2283 if (not defined $ldap_handle) {
2284 daemon_log("$session_id ERROR: no ldap_handle available to create_packages_list_db", 1);
2285 unlink($packages_list_under_construction);
2286 return;
2287 }
2288 }
2289 if (not defined $sources_file) {
2290 &main::daemon_log("$session_id INFO: no sources_file given for creating packages list so trigger creation of it", 5);
2291 $sources_file = &create_sources_list($session_id);
2292 }
2294 if (not defined $sources_file) {
2295 &main::daemon_log("$session_id ERROR: no sources_file given under '$sources_file', skip create_packages_list_db", 1);
2296 unlink($packages_list_under_construction);
2297 return;
2298 }
2300 my $line;
2302 open(CONFIG, "<$sources_file") or do {
2303 daemon_log( "$session_id ERROR: create_packages_list_db: Failed to open '$sources_file'", 1);
2304 unlink($packages_list_under_construction);
2305 return;
2306 };
2308 # Read lines
2309 while ($line = <CONFIG>){
2310 # Unify
2311 chop($line);
2312 $line =~ s/^\s+//;
2313 $line =~ s/^\s+/ /;
2315 # Strip comments
2316 $line =~ s/#.*$//g;
2318 # Skip empty lines
2319 if ($line =~ /^\s*$/){
2320 next;
2321 }
2323 # Interpret deb line
2324 if ($line =~ /^deb [^\s]+\s[^\s]+\s[^\s]+/){
2325 my( $baseurl, $dist, $sections ) = ($line =~ /^deb\s([^\s]+)\s+([^\s]+)\s+(.*)$/);
2326 my $section;
2327 foreach $section (split(' ', $sections)){
2328 &parse_package_info( $baseurl, $dist, $section, $session_id );
2329 }
2330 }
2331 }
2333 close (CONFIG);
2335 find(\&cleanup_and_extract, keys( %repo_dirs ));
2336 &main::strip_packages_list_statements();
2337 unshift @packages_list_statements, "VACUUM";
2338 $packages_list_db->exec_statementlist(\@packages_list_statements);
2339 unlink($packages_list_under_construction);
2340 daemon_log("$session_id INFO: create_packages_list_db: finished", 5);
2341 return;
2342 }
2344 # This function should do some intensive task to minimize the db-traffic
2345 sub strip_packages_list_statements {
2346 my @existing_entries= @{$packages_list_db->exec_statement("SELECT * FROM $main::packages_list_tn")};
2347 my @new_statement_list=();
2348 my $hash;
2349 my $insert_hash;
2350 my $update_hash;
2351 my $delete_hash;
2352 my $local_timestamp=get_time();
2354 foreach my $existing_entry (@existing_entries) {
2355 $hash->{@{$existing_entry}[0]}->{@{$existing_entry}[1]}->{@{$existing_entry}[2]}= $existing_entry;
2356 }
2358 foreach my $statement (@packages_list_statements) {
2359 if($statement =~ /^INSERT/i) {
2360 # Assign the values from the insert statement
2361 my ($distribution,$package,$version,$section,$description,$template,$timestamp) = ($1,$2,$3,$4,$5,$6,$7) if $statement =~
2362 /^INSERT\s+?INTO\s+?$main::packages_list_tn\s+?VALUES\s*?\('(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)'\s*?\)$/si;
2363 if(exists($hash->{$distribution}->{$package}->{$version})) {
2364 # If section or description has changed, update the DB
2365 if(
2366 (! (@{$hash->{$distribution}->{$package}->{$version}}[3] eq $section)) or
2367 (! (@{$hash->{$distribution}->{$package}->{$version}}[4] eq $description))
2368 ) {
2369 @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,undef);
2370 }
2371 } else {
2372 # Insert a non-existing entry to db
2373 @{$insert_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
2374 }
2375 } elsif ($statement =~ /^UPDATE/i) {
2376 my ($template,$package,$version) = ($1,$2,$3) if $statement =~
2377 /^update\s+?$main::packages_list_tn\s+?set\s+?template\s*?=\s*?'(.*?)'\s+?where\s+?package\s*?=\s*?'(.*?)'\s+?and\s+?version\s*?=\s*?'(.*?)'\s*?;$/si;
2378 foreach my $distribution (keys %{$hash}) {
2379 if(exists($insert_hash->{$distribution}->{$package}->{$version})) {
2380 # update the insertion hash to execute only one query per package (insert instead insert+update)
2381 @{$insert_hash->{$distribution}->{$package}->{$version}}[5]= $template;
2382 } elsif(exists($hash->{$distribution}->{$package}->{$version})) {
2383 if( ! (@{$hash->{$distribution}->{$package}->{$version}}[5] eq $template)) {
2384 my $section;
2385 my $description;
2386 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) and
2387 length(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) > 0 ) {
2388 $section= @{$update_hash->{$distribution}->{$package}->{$version}}[3];
2389 }
2390 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
2391 $description= @{$update_hash->{$distribution}->{$package}->{$version}}[4];
2392 }
2393 @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
2394 }
2395 }
2396 }
2397 }
2398 }
2400 # TODO: Check for orphaned entries
2402 # unroll the insert_hash
2403 foreach my $distribution (keys %{$insert_hash}) {
2404 foreach my $package (keys %{$insert_hash->{$distribution}}) {
2405 foreach my $version (keys %{$insert_hash->{$distribution}->{$package}}) {
2406 push @new_statement_list, "INSERT INTO $main::packages_list_tn VALUES ('$distribution','$package','$version',"
2407 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[3]',"
2408 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[4]',"
2409 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[5]',"
2410 ."'$local_timestamp')";
2411 }
2412 }
2413 }
2415 # unroll the update hash
2416 foreach my $distribution (keys %{$update_hash}) {
2417 foreach my $package (keys %{$update_hash->{$distribution}}) {
2418 foreach my $version (keys %{$update_hash->{$distribution}->{$package}}) {
2419 my $set = "";
2420 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3])) {
2421 $set .= "section = '@{$update_hash->{$distribution}->{$package}->{$version}}[3]', ";
2422 }
2423 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
2424 $set .= "description = '@{$update_hash->{$distribution}->{$package}->{$version}}[4]', ";
2425 }
2426 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[5])) {
2427 $set .= "template = '@{$update_hash->{$distribution}->{$package}->{$version}}[5]', ";
2428 }
2429 if(defined($set) and length($set) > 0) {
2430 $set .= "timestamp = '$local_timestamp'";
2431 } else {
2432 next;
2433 }
2434 push @new_statement_list,
2435 "UPDATE $main::packages_list_tn SET $set WHERE"
2436 ." distribution = '$distribution'"
2437 ." AND package = '$package'"
2438 ." AND version = '$version'";
2439 }
2440 }
2441 }
2443 @packages_list_statements = @new_statement_list;
2444 }
2447 sub parse_package_info {
2448 my ($baseurl, $dist, $section, $session_id)= @_;
2449 my ($package);
2450 if (not defined $session_id) { $session_id = 0; }
2451 my ($path) = ($baseurl =~ m%://[^/]*(.*)$%);
2452 $repo_dirs{ "${repo_path}/pool" } = 1;
2454 foreach $package ("Packages.gz"){
2455 daemon_log("$session_id DEBUG: create_packages_list: fetch $baseurl, $dist, $section", 7);
2456 get_package( "$baseurl/dists/$dist/$section/binary-$arch/$package", "$outdir/$dist/$section", $session_id );
2457 parse_package( "$outdir/$dist/$section", $dist, $path, $session_id );
2458 }
2460 }
2463 sub get_package {
2464 my ($url, $dest, $session_id)= @_;
2465 if (not defined $session_id) { $session_id = 0; }
2467 my $tpath = dirname($dest);
2468 -d "$tpath" || mkpath "$tpath";
2470 # This is ugly, but I've no time to take a look at "how it works in perl"
2471 if(0 == system("wget '$url' -O '$dest' 2>/dev/null") ) {
2472 system("gunzip -cd '$dest' > '$dest.in'");
2473 daemon_log("$session_id DEBUG: run command: gunzip -cd '$dest' > '$dest.in'", 5);
2474 unlink($dest);
2475 daemon_log("$session_id DEBUG: delete file '$dest'", 5);
2476 } else {
2477 daemon_log("$session_id ERROR: create_packages_list_db: get_packages: fetching '$url' failed!", 1);
2478 }
2479 return 0;
2480 }
2483 sub parse_package {
2484 my ($path, $dist, $srv_path, $session_id)= @_;
2485 if (not defined $session_id) { $session_id = 0;}
2486 my ($package, $version, $section, $description);
2487 my $PACKAGES;
2488 my $timestamp = &get_time();
2490 if(not stat("$path.in")) {
2491 daemon_log("$session_id ERROR: create_packages_list: parse_package: file '$path.in' is not readable",1);
2492 return;
2493 }
2495 open($PACKAGES, "<$path.in");
2496 if(not defined($PACKAGES)) {
2497 daemon_log("$session_id ERROR: create_packages_list_db: parse_package: cannot open '$path.in'",1);
2498 return;
2499 }
2501 # Read lines
2502 while (<$PACKAGES>){
2503 my $line = $_;
2504 # Unify
2505 chop($line);
2507 # Use empty lines as a trigger
2508 if ($line =~ /^\s*$/){
2509 my $sql = "INSERT INTO packages_list VALUES ('$dist', '$package', '$version', '$section', '$description', '', '$timestamp')";
2510 push(@packages_list_statements, $sql);
2511 $package = "none";
2512 $version = "none";
2513 $section = "none";
2514 $description = "none";
2515 next;
2516 }
2518 # Trigger for package name
2519 if ($line =~ /^Package:\s/){
2520 ($package)= ($line =~ /^Package: (.*)$/);
2521 next;
2522 }
2524 # Trigger for version
2525 if ($line =~ /^Version:\s/){
2526 ($version)= ($line =~ /^Version: (.*)$/);
2527 next;
2528 }
2530 # Trigger for description
2531 if ($line =~ /^Description:\s/){
2532 ($description)= &encode_base64(($line =~ /^Description: (.*)$/));
2533 next;
2534 }
2536 # Trigger for section
2537 if ($line =~ /^Section:\s/){
2538 ($section)= ($line =~ /^Section: (.*)$/);
2539 next;
2540 }
2542 # Trigger for filename
2543 if ($line =~ /^Filename:\s/){
2544 my ($filename) = ($line =~ /^Filename: (.*)$/);
2545 store_fileinfo( $package, $filename, $dist, $srv_path, $version, $repo_path );
2546 next;
2547 }
2548 }
2550 close( $PACKAGES );
2551 unlink( "$path.in" );
2552 &main::daemon_log("$session_id DEBUG: unlink '$path.in'", 1);
2553 }
2556 sub store_fileinfo {
2557 my( $package, $file, $dist, $path, $vers, $srvdir) = @_;
2559 my %fileinfo = (
2560 'package' => $package,
2561 'dist' => $dist,
2562 'version' => $vers,
2563 );
2565 $repo_files{ "${srvdir}/$file" } = \%fileinfo;
2566 }
2569 sub cleanup_and_extract {
2570 my $fileinfo = $repo_files{ $File::Find::name };
2572 if( defined $fileinfo ) {
2574 my $dir = "$outdir/$fileinfo->{ 'dist' }/debconf.d";
2575 my $sql;
2576 my $package = $fileinfo->{ 'package' };
2577 my $newver = $fileinfo->{ 'version' };
2579 mkpath($dir);
2580 system( "dpkg -e '$File::Find::name' '$dir/DEBIAN'" );
2582 if( -f "$dir/DEBIAN/templates" ) {
2584 daemon_log("DEBUG: Found debconf templates in '$package' - $newver", 5);
2586 my $tmpl= "";
2587 {
2588 local $/=undef;
2589 open FILE, "$dir/DEBIAN/templates";
2590 $tmpl = &encode_base64(<FILE>);
2591 close FILE;
2592 }
2593 rmtree("$dir/DEBIAN/templates");
2595 $sql= "update $main::packages_list_tn set template = '$tmpl' where package = '$package' and version = '$newver';";
2596 push @packages_list_statements, $sql;
2597 }
2598 }
2600 return;
2601 }
2604 #==== MAIN = main ==============================================================
2605 # parse commandline options
2606 Getopt::Long::Configure( "bundling" );
2607 GetOptions("h|help" => \&usage,
2608 "c|config=s" => \$cfg_file,
2609 "f|foreground" => \$foreground,
2610 "v|verbose+" => \$verbose,
2611 "no-bus+" => \$no_bus,
2612 "no-arp+" => \$no_arp,
2613 );
2615 # read and set config parameters
2616 &check_cmdline_param ;
2617 &read_configfile;
2618 &check_pid;
2620 $SIG{CHLD} = 'IGNORE';
2622 # forward error messages to logfile
2623 if( ! $foreground ) {
2624 open( STDIN, '+>/dev/null' );
2625 open( STDOUT, '+>&STDIN' );
2626 open( STDERR, '+>&STDIN' );
2627 }
2629 # Just fork, if we are not in foreground mode
2630 if( ! $foreground ) {
2631 chdir '/' or die "Can't chdir to /: $!";
2632 $pid = fork;
2633 setsid or die "Can't start a new session: $!";
2634 umask 0;
2635 } else {
2636 $pid = $$;
2637 }
2639 # Do something useful - put our PID into the pid_file
2640 if( 0 != $pid ) {
2641 open( LOCK_FILE, ">$pid_file" );
2642 print LOCK_FILE "$pid\n";
2643 close( LOCK_FILE );
2644 if( !$foreground ) {
2645 exit( 0 )
2646 };
2647 }
2649 # parse head url and revision from svn
2650 my $server_status_hash = { 'developmental'=>'revision', 'stable'=>'release'};
2651 $server_version =~ /^\$HeadURL: (\S+) \$:\$Rev: (\d+) \$$/;
2652 $server_headURL = defined $1 ? $1 : 'unknown' ;
2653 $server_revision = defined $2 ? $2 : 'unknown' ;
2654 if ($server_headURL =~ /\/tag\// ||
2655 $server_headURL =~ /\/branches\// ) {
2656 $server_status = "stable";
2657 } else {
2658 $server_status = "developmental" ;
2659 }
2662 daemon_log(" ", 1);
2663 daemon_log("$0 started!", 1);
2664 daemon_log("status: $server_status", 1);
2665 daemon_log($server_status_hash->{$server_status}.": $server_revision", 1);
2667 if ($no_bus > 0) {
2668 $bus_activ = "false"
2669 }
2671 # connect to incoming_db
2672 unlink($incoming_file_name);
2673 $incoming_db = GOSA::DBsqlite->new($incoming_file_name);
2674 $incoming_db->create_table($incoming_tn, \@incoming_col_names);
2676 # connect to gosa-si job queue
2677 $job_db = GOSA::DBsqlite->new($job_queue_file_name);
2678 $job_db->create_table($job_queue_tn, \@job_queue_col_names);
2680 # connect to known_clients_db
2681 $known_clients_db = GOSA::DBsqlite->new($known_clients_file_name);
2682 $known_clients_db->create_table($known_clients_tn, \@known_clients_col_names);
2684 # connect to known_server_db
2685 $known_server_db = GOSA::DBsqlite->new($known_server_file_name);
2686 $known_server_db->create_table($known_server_tn, \@known_server_col_names);
2688 # connect to login_usr_db
2689 $login_users_db = GOSA::DBsqlite->new($login_users_file_name);
2690 $login_users_db->create_table($login_users_tn, \@login_users_col_names);
2692 # connect to fai_server_db and fai_release_db
2693 unlink($fai_server_file_name);
2694 $fai_server_db = GOSA::DBsqlite->new($fai_server_file_name);
2695 $fai_server_db->create_table($fai_server_tn, \@fai_server_col_names);
2697 unlink($fai_release_file_name);
2698 $fai_release_db = GOSA::DBsqlite->new($fai_release_file_name);
2699 $fai_release_db->create_table($fai_release_tn, \@fai_release_col_names);
2701 # connect to packages_list_db
2702 #unlink($packages_list_file_name);
2703 unlink($packages_list_under_construction);
2704 $packages_list_db = GOSA::DBsqlite->new($packages_list_file_name);
2705 $packages_list_db->create_table($packages_list_tn, \@packages_list_col_names);
2707 # connect to messaging_db
2708 $messaging_db = GOSA::DBsqlite->new($messaging_file_name);
2709 $messaging_db->create_table($messaging_tn, \@messaging_col_names);
2712 # create xml object used for en/decrypting
2713 $xml = new XML::Simple();
2715 # create socket for incoming xml messages
2717 POE::Component::Server::TCP->new(
2718 Port => $server_port,
2719 ClientInput => sub {
2720 my ($kernel, $input) = @_[KERNEL, ARG0];
2721 push(@tasks, $input);
2722 push(@msgs_to_decrypt, $input);
2723 $kernel->yield("next_task");
2724 $kernel->yield("msg_to_decrypt");
2725 },
2726 InlineStates => {
2727 next_task => \&next_task,
2728 msg_to_decrypt => \&msg_to_decrypt,
2729 task_result => \&handle_task_result,
2730 task_done => \&handle_task_done,
2731 task_debug => \&handle_task_debug,
2732 child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!" },
2733 }
2734 );
2736 daemon_log("start socket for incoming xml messages at port '$server_port' ", 1);
2738 # create session for repeatedly checking the job queue for jobs
2739 POE::Session->create(
2740 inline_states => {
2741 _start => \&_start,
2742 sig_handler => \&sig_handler,
2743 watch_for_new_messages => \&watch_for_new_messages,
2744 watch_for_delivery_messages => \&watch_for_delivery_messages,
2745 watch_for_done_messages => \&watch_for_done_messages,
2746 watch_for_new_jobs => \&watch_for_new_jobs,
2747 watch_for_done_jobs => \&watch_for_done_jobs,
2748 create_packages_list_db => \&run_create_packages_list_db,
2749 create_fai_server_db => \&run_create_fai_server_db,
2750 create_fai_release_db => \&run_create_fai_release_db,
2751 session_run_result => \&session_run_result,
2752 session_run_debug => \&session_run_debug,
2753 session_run_done => \&session_run_done,
2754 child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!" },
2755 }
2756 );
2759 # import all modules
2760 &import_modules;
2762 # check wether all modules are gosa-si valid passwd check
2764 POE::Kernel->run();
2765 exit;