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