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, $ClientPackages_key,
86 $arp_activ, $gosa_unit_tag,
87 $GosaPackages_key, $gosa_ip, $gosa_port, $gosa_timeout,
88 $foreign_server_string, $server_domain, $ServerPackages_key, $foreign_servers_register_delay,
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 "ClientPackages" => {
237 "key" => [\$ClientPackages_key, "none"],
238 },
239 "ServerPackages"=> {
240 "address" => [\$foreign_server_string, ""],
241 "domain" => [\$server_domain, ""],
242 "key" => [\$ServerPackages_key, "none"],
243 "key-lifetime" => [\$foreign_servers_register_delay, 600],
244 }
245 );
248 #=== FUNCTION ================================================================
249 # NAME: usage
250 # PARAMETERS: nothing
251 # RETURNS: nothing
252 # DESCRIPTION: print out usage text to STDERR
253 #===============================================================================
254 sub usage {
255 print STDERR << "EOF" ;
256 usage: $prg [-hvf] [-c config]
258 -h : this (help) message
259 -c <file> : config file
260 -f : foreground, process will not be forked to background
261 -v : be verbose (multiple to increase verbosity)
262 -no-bus : starts $prg without connection to bus
263 -no-arp : starts $prg without connection to arp module
265 EOF
266 print "\n" ;
267 }
270 #=== FUNCTION ================================================================
271 # NAME: read_configfile
272 # PARAMETERS: cfg_file - string -
273 # RETURNS: nothing
274 # DESCRIPTION: read cfg_file and set variables
275 #===============================================================================
276 sub read_configfile {
277 my $cfg;
278 if( defined( $cfg_file) && ( (-s $cfg_file) > 0 )) {
279 if( -r $cfg_file ) {
280 $cfg = Config::IniFiles->new( -file => $cfg_file );
281 } else {
282 print STDERR "Couldn't read config file!\n";
283 }
284 } else {
285 $cfg = Config::IniFiles->new() ;
286 }
287 foreach my $section (keys %cfg_defaults) {
288 foreach my $param (keys %{$cfg_defaults{ $section }}) {
289 my $pinfo = $cfg_defaults{ $section }{ $param };
290 ${@$pinfo[ 0 ]} = $cfg->val( $section, $param, @$pinfo[ 1 ] );
291 }
292 }
293 }
296 #=== FUNCTION ================================================================
297 # NAME: logging
298 # PARAMETERS: level - string - default 'info'
299 # msg - string -
300 # facility - string - default 'LOG_DAEMON'
301 # RETURNS: nothing
302 # DESCRIPTION: function for logging
303 #===============================================================================
304 sub daemon_log {
305 # log into log_file
306 my( $msg, $level ) = @_;
307 if(not defined $msg) { return }
308 if(not defined $level) { $level = 1 }
309 if(defined $log_file){
310 open(LOG_HANDLE, ">>$log_file");
311 if(not defined open( LOG_HANDLE, ">>$log_file" )) {
312 print STDERR "cannot open $log_file: $!";
313 return }
314 chomp($msg);
315 $msg =~s/\n//g; # no newlines are allowed in log messages, this is important for later log parsing
316 if($level <= $verbose){
317 my ($seconds, $minutes, $hours, $monthday, $month,
318 $year, $weekday, $yearday, $sommertime) = localtime(time);
319 $hours = $hours < 10 ? $hours = "0".$hours : $hours;
320 $minutes = $minutes < 10 ? $minutes = "0".$minutes : $minutes;
321 $seconds = $seconds < 10 ? $seconds = "0".$seconds : $seconds;
322 my @monthnames = ("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec");
323 $month = $monthnames[$month];
324 $monthday = $monthday < 10 ? $monthday = "0".$monthday : $monthday;
325 $year+=1900;
326 my $name = $prg;
328 my $log_msg = "$month $monthday $hours:$minutes:$seconds $name $msg\n";
329 print LOG_HANDLE $log_msg;
330 if( $foreground ) {
331 print STDERR $log_msg;
332 }
333 }
334 close( LOG_HANDLE );
335 }
336 }
339 #=== FUNCTION ================================================================
340 # NAME: check_cmdline_param
341 # PARAMETERS: nothing
342 # RETURNS: nothing
343 # DESCRIPTION: validates commandline parameter
344 #===============================================================================
345 sub check_cmdline_param () {
346 my $err_config;
347 my $err_counter = 0;
348 if(not defined($cfg_file)) {
349 $cfg_file = "/etc/gosa-si/server.conf";
350 if(! -r $cfg_file) {
351 $err_config = "please specify a config file";
352 $err_counter += 1;
353 }
354 }
355 if( $err_counter > 0 ) {
356 &usage( "", 1 );
357 if( defined( $err_config)) { print STDERR "$err_config\n"}
358 print STDERR "\n";
359 exit( -1 );
360 }
361 }
364 #=== FUNCTION ================================================================
365 # NAME: check_pid
366 # PARAMETERS: nothing
367 # RETURNS: nothing
368 # DESCRIPTION: handels pid processing
369 #===============================================================================
370 sub check_pid {
371 $pid = -1;
372 # Check, if we are already running
373 if( open(LOCK_FILE, "<$pid_file") ) {
374 $pid = <LOCK_FILE>;
375 if( defined $pid ) {
376 chomp( $pid );
377 if( -f "/proc/$pid/stat" ) {
378 my($stat) = `cat /proc/$pid/stat` =~ m/$pid \((.+)\).*/;
379 if( $stat ) {
380 daemon_log("ERROR: Already running",1);
381 close( LOCK_FILE );
382 exit -1;
383 }
384 }
385 }
386 close( LOCK_FILE );
387 unlink( $pid_file );
388 }
390 # create a syslog msg if it is not to possible to open PID file
391 if (not sysopen(LOCK_FILE, $pid_file, O_WRONLY|O_CREAT|O_EXCL, 0644)) {
392 my($msg) = "Couldn't obtain lockfile '$pid_file' ";
393 if (open(LOCK_FILE, '<', $pid_file)
394 && ($pid = <LOCK_FILE>))
395 {
396 chomp($pid);
397 $msg .= "(PID $pid)\n";
398 } else {
399 $msg .= "(unable to read PID)\n";
400 }
401 if( ! ($foreground) ) {
402 openlog( $0, "cons,pid", "daemon" );
403 syslog( "warning", $msg );
404 closelog();
405 }
406 else {
407 print( STDERR " $msg " );
408 }
409 exit( -1 );
410 }
411 }
413 #=== FUNCTION ================================================================
414 # NAME: import_modules
415 # PARAMETERS: module_path - string - abs. path to the directory the modules
416 # are stored
417 # RETURNS: nothing
418 # DESCRIPTION: each file in module_path which ends with '.pm' and activation
419 # state is on is imported by "require 'file';"
420 #===============================================================================
421 sub import_modules {
422 daemon_log(" ", 1);
424 if (not -e $modules_path) {
425 daemon_log("0 ERROR: cannot find directory or directory is not readable: $modules_path", 1);
426 }
428 opendir (DIR, $modules_path) or die "ERROR while loading modules from directory $modules_path : $!\n";
429 while (defined (my $file = readdir (DIR))) {
430 if (not $file =~ /(\S*?).pm$/) {
431 next;
432 }
433 my $mod_name = $1;
435 if( $file =~ /ArpHandler.pm/ ) {
436 if( $no_arp > 0 ) {
437 next;
438 }
439 }
441 eval { require $file; };
442 if ($@) {
443 daemon_log("0 ERROR: gosa-si-server could not load module $file", 1);
444 daemon_log("$@", 5);
445 } else {
446 my $info = eval($mod_name.'::get_module_info()');
447 # Only load module if get_module_info() returns a non-null object
448 if( $info ) {
449 my ($input_address, $input_key, $input, $input_active, $input_type) = @{$info};
450 $known_modules->{$mod_name} = $info;
451 daemon_log("0 INFO: module $mod_name loaded", 5);
452 }
453 }
454 }
455 close (DIR);
456 }
459 #=== FUNCTION ================================================================
460 # NAME: sig_int_handler
461 # PARAMETERS: signal - string - signal arose from system
462 # RETURNS: noting
463 # DESCRIPTION: handels tasks to be done befor signal becomes active
464 #===============================================================================
465 sub sig_int_handler {
466 my ($signal) = @_;
468 # if (defined($ldap_handle)) {
469 # $ldap_handle->disconnect;
470 # }
471 # TODO alle verbliebenden ldap verbindungen aus allen heaps beenden
474 daemon_log("shutting down gosa-si-server", 1);
475 system("kill `ps -C gosa-si-server-nobus -o pid=`");
476 }
477 $SIG{INT} = \&sig_int_handler;
480 sub check_key_and_xml_validity {
481 my ($crypted_msg, $module_key, $session_id) = @_;
482 my $msg;
483 my $msg_hash;
484 my $error_string;
485 eval{
486 $msg = &decrypt_msg($crypted_msg, $module_key);
488 if ($msg =~ /<xml>/i){
489 $msg =~ s/\s+/ /g; # just for better daemon_log
490 daemon_log("$session_id DEBUG: decrypted_msg: \n$msg", 8);
491 $msg_hash = $xml->XMLin($msg, ForceArray=>1);
493 ##############
494 # check header
495 if( not exists $msg_hash->{'header'} ) { die "no header specified"; }
496 my $header_l = $msg_hash->{'header'};
497 if( 1 > @{$header_l} ) { die 'empty header tag'; }
498 if( 1 < @{$header_l} ) { die 'more than one header specified'; }
499 my $header = @{$header_l}[0];
500 if( 0 == length $header) { die 'empty string in header tag'; }
502 ##############
503 # check source
504 if( not exists $msg_hash->{'source'} ) { die "no source specified"; }
505 my $source_l = $msg_hash->{'source'};
506 if( 1 > @{$source_l} ) { die 'empty source tag'; }
507 if( 1 < @{$source_l} ) { die 'more than one source specified'; }
508 my $source = @{$source_l}[0];
509 if( 0 == length $source) { die 'source error'; }
511 ##############
512 # check target
513 if( not exists $msg_hash->{'target'} ) { die "no target specified"; }
514 my $target_l = $msg_hash->{'target'};
515 if( 1 > @{$target_l} ) { die 'empty target tag'; }
516 }
517 };
518 if($@) {
519 daemon_log("$session_id DEBUG: do not understand the message: $@", 7);
520 $msg = undef;
521 $msg_hash = undef;
522 }
524 return ($msg, $msg_hash);
525 }
528 sub check_outgoing_xml_validity {
529 my ($msg) = @_;
531 my $msg_hash;
532 eval{
533 $msg_hash = $xml->XMLin($msg, ForceArray=>1);
535 ##############
536 # check header
537 my $header_l = $msg_hash->{'header'};
538 if( 1 != @{$header_l} ) {
539 die 'no or more than one headers specified';
540 }
541 my $header = @{$header_l}[0];
542 if( 0 == length $header) {
543 die 'header has length 0';
544 }
546 ##############
547 # check source
548 my $source_l = $msg_hash->{'source'};
549 if( 1 != @{$source_l} ) {
550 die 'no or more than 1 sources specified';
551 }
552 my $source = @{$source_l}[0];
553 if( 0 == length $source) {
554 die 'source has length 0';
555 }
556 unless( $source =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
557 $source =~ /^GOSA$/i ) {
558 die "source '$source' is neither a complete ip-address with port nor 'GOSA'";
559 }
561 ##############
562 # check target
563 my $target_l = $msg_hash->{'target'};
564 if( 0 == @{$target_l} ) {
565 die "no targets specified";
566 }
567 foreach my $target (@$target_l) {
568 if( 0 == length $target) {
569 die "target has length 0";
570 }
571 unless( $target =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
572 $target =~ /^GOSA$/i ||
573 $target =~ /^\*$/ ||
574 $target =~ /KNOWN_SERVER/i ||
575 $target =~ /JOBDB/i ||
576 $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 ){
577 die "target '$target' is not a complete ip-address with port or a valid target name or a mac-address";
578 }
579 }
580 };
581 if($@) {
582 daemon_log("WARNING: outgoing msg is not gosa-si envelope conform", 5);
583 daemon_log("$@ ".(defined($msg) && length($msg)>0)?$msg:"Empty Message", 8);
584 $msg_hash = undef;
585 }
587 return ($msg_hash);
588 }
591 sub input_from_known_server {
592 my ($input, $remote_ip, $session_id) = @_ ;
593 my ($msg, $msg_hash, $module);
595 my $sql_statement= "SELECT * FROM known_server";
596 my $query_res = $known_server_db->select_dbentry( $sql_statement );
598 while( my ($hit_num, $hit) = each %{ $query_res } ) {
599 my $host_name = $hit->{hostname};
600 if( not $host_name =~ "^$remote_ip") {
601 next;
602 }
603 my $host_key = $hit->{hostkey};
604 daemon_log("$session_id DEBUG: input_from_known_server: host_name: $host_name", 7);
605 daemon_log("$session_id DEBUG: input_from_known_server: host_key: $host_key", 7);
607 # check if module can open msg envelope with module key
608 my ($tmp_msg, $tmp_msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
609 if( (!$tmp_msg) || (!$tmp_msg_hash) ) {
610 daemon_log("$session_id DEBUG: input_from_known_server: deciphering raise error", 7);
611 daemon_log("$@", 8);
612 next;
613 }
614 else {
615 $msg = $tmp_msg;
616 $msg_hash = $tmp_msg_hash;
617 $module = "ServerPackages";
618 last;
619 }
620 }
622 if( (!$msg) || (!$msg_hash) || (!$module) ) {
623 daemon_log("$session_id DEBUG: Incoming message is not from a known server", 7);
624 }
626 return ($msg, $msg_hash, $module);
627 }
630 sub input_from_known_client {
631 my ($input, $remote_ip, $session_id) = @_ ;
632 my ($msg, $msg_hash, $module);
634 my $sql_statement= "SELECT * FROM known_clients";
635 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
636 while( my ($hit_num, $hit) = each %{ $query_res } ) {
637 my $host_name = $hit->{hostname};
638 if( not $host_name =~ /^$remote_ip:\d*$/) {
639 next;
640 }
641 my $host_key = $hit->{hostkey};
642 &daemon_log("$session_id DEBUG: input_from_known_client: host_name: $host_name", 7);
643 &daemon_log("$session_id DEBUG: input_from_known_client: host_key: $host_key", 7);
645 # check if module can open msg envelope with module key
646 ($msg, $msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
648 if( (!$msg) || (!$msg_hash) ) {
649 &daemon_log("$session_id DEGUG: input_from_known_client: deciphering raise error", 7);
650 &daemon_log("$@", 8);
651 next;
652 }
653 else {
654 $module = "ClientPackages";
655 last;
656 }
657 }
659 if( (!$msg) || (!$msg_hash) || (!$module) ) {
660 &daemon_log("$session_id DEBUG: Incoming message is not from a known client", 7);
661 }
663 return ($msg, $msg_hash, $module);
664 }
667 sub input_from_unknown_host {
668 no strict "refs";
669 my ($input, $session_id) = @_ ;
670 my ($msg, $msg_hash, $module);
671 my $error_string;
673 my %act_modules = %$known_modules;
675 while( my ($mod, $info) = each(%act_modules)) {
677 # check a key exists for this module
678 my $module_key = ${$mod."_key"};
679 if( not defined $module_key ) {
680 if( $mod eq 'ArpHandler' ) {
681 next;
682 }
683 daemon_log("$session_id ERROR: no key specified in config file for $mod", 1);
684 next;
685 }
686 daemon_log("$session_id DEBUG: $mod: $module_key", 7);
688 # check if module can open msg envelope with module key
689 ($msg, $msg_hash) = &check_key_and_xml_validity($input, $module_key, $session_id);
690 if( (not defined $msg) || (not defined $msg_hash) ) {
691 next;
692 }
693 else {
694 $module = $mod;
695 last;
696 }
697 }
699 if( (!$msg) || (!$msg_hash) || (!$module)) {
700 daemon_log("$session_id DEBUG: Incoming message is not from an unknown host", 7);
701 }
703 return ($msg, $msg_hash, $module);
704 }
707 sub create_ciphering {
708 my ($passwd) = @_;
709 if((!defined($passwd)) || length($passwd)==0) {
710 $passwd = "";
711 }
712 $passwd = substr(md5_hex("$passwd") x 32, 0, 32);
713 my $iv = substr(md5_hex('GONICUS GmbH'),0, 16);
714 my $my_cipher = Crypt::Rijndael->new($passwd , Crypt::Rijndael::MODE_CBC());
715 $my_cipher->set_iv($iv);
716 return $my_cipher;
717 }
720 sub encrypt_msg {
721 my ($msg, $key) = @_;
722 my $my_cipher = &create_ciphering($key);
723 my $len;
724 {
725 use bytes;
726 $len= 16-length($msg)%16;
727 }
728 $msg = "\0"x($len).$msg;
729 $msg = $my_cipher->encrypt($msg);
730 chomp($msg = &encode_base64($msg));
731 # there are no newlines allowed inside msg
732 $msg=~ s/\n//g;
733 return $msg;
734 }
737 sub decrypt_msg {
739 my ($msg, $key) = @_ ;
740 $msg = &decode_base64($msg);
741 my $my_cipher = &create_ciphering($key);
742 $msg = $my_cipher->decrypt($msg);
743 $msg =~ s/\0*//g;
744 return $msg;
745 }
748 sub get_encrypt_key {
749 my ($target) = @_ ;
750 my $encrypt_key;
751 my $error = 0;
753 # target can be in known_server
754 if( not defined $encrypt_key ) {
755 my $sql_statement= "SELECT * FROM known_server WHERE hostname='$target'";
756 my $query_res = $known_server_db->select_dbentry( $sql_statement );
757 while( my ($hit_num, $hit) = each %{ $query_res } ) {
758 my $host_name = $hit->{hostname};
759 if( $host_name ne $target ) {
760 next;
761 }
762 $encrypt_key = $hit->{hostkey};
763 last;
764 }
765 }
767 # target can be in known_client
768 if( not defined $encrypt_key ) {
769 my $sql_statement= "SELECT * FROM known_clients WHERE hostname='$target'";
770 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
771 while( my ($hit_num, $hit) = each %{ $query_res } ) {
772 my $host_name = $hit->{hostname};
773 if( $host_name ne $target ) {
774 next;
775 }
776 $encrypt_key = $hit->{hostkey};
777 last;
778 }
779 }
781 return $encrypt_key;
782 }
785 #=== FUNCTION ================================================================
786 # NAME: open_socket
787 # PARAMETERS: PeerAddr string something like 192.168.1.1 or 192.168.1.1:10000
788 # [PeerPort] string necessary if port not appended by PeerAddr
789 # RETURNS: socket IO::Socket::INET
790 # DESCRIPTION: open a socket to PeerAddr
791 #===============================================================================
792 sub open_socket {
793 my ($PeerAddr, $PeerPort) = @_ ;
794 if(defined($PeerPort)){
795 $PeerAddr = $PeerAddr.":".$PeerPort;
796 }
797 my $socket;
798 $socket = new IO::Socket::INET(PeerAddr => $PeerAddr,
799 Porto => "tcp",
800 Type => SOCK_STREAM,
801 Timeout => 5,
802 );
803 if(not defined $socket) {
804 return;
805 }
806 # &daemon_log("DEBUG: open_socket: $PeerAddr", 7);
807 return $socket;
808 }
811 #=== FUNCTION ================================================================
812 # NAME: get_ip
813 # PARAMETERS: interface name (i.e. eth0)
814 # RETURNS: (ip address)
815 # DESCRIPTION: Uses ioctl to get ip address directly from system.
816 #===============================================================================
817 sub get_ip {
818 my $ifreq= shift;
819 my $result= "";
820 my $SIOCGIFADDR= 0x8915; # man 2 ioctl_list
821 my $proto= getprotobyname('ip');
823 socket SOCKET, PF_INET, SOCK_DGRAM, $proto
824 or die "socket: $!";
826 if(ioctl SOCKET, $SIOCGIFADDR, $ifreq) {
827 my ($if, $sin) = unpack 'a16 a16', $ifreq;
828 my ($port, $addr) = sockaddr_in $sin;
829 my $ip = inet_ntoa $addr;
831 if ($ip && length($ip) > 0) {
832 $result = $ip;
833 }
834 }
836 return $result;
837 }
840 sub get_local_ip_for_remote_ip {
841 my $remote_ip= shift;
842 my $result="0.0.0.0";
844 if($remote_ip =~ /^(\d\d?\d?\.){3}\d\d?\d?$/) {
845 if($remote_ip eq "127.0.0.1") {
846 $result = "127.0.0.1";
847 } else {
848 my $PROC_NET_ROUTE= ('/proc/net/route');
850 open(PROC_NET_ROUTE, "<$PROC_NET_ROUTE")
851 or die "Could not open $PROC_NET_ROUTE";
853 my @ifs = <PROC_NET_ROUTE>;
855 close(PROC_NET_ROUTE);
857 # Eat header line
858 shift @ifs;
859 chomp @ifs;
860 foreach my $line(@ifs) {
861 my ($Iface,$Destination,$Gateway,$Flags,$RefCnt,$Use,$Metric,$Mask,$MTU,$Window,$IRTT)=split(/\s/, $line);
862 my $destination;
863 my $mask;
864 my ($d,$c,$b,$a)=unpack('a2 a2 a2 a2', $Destination);
865 $destination= sprintf("%d.%d.%d.%d", hex($a), hex($b), hex($c), hex($d));
866 ($d,$c,$b,$a)=unpack('a2 a2 a2 a2', $Mask);
867 $mask= sprintf("%d.%d.%d.%d", hex($a), hex($b), hex($c), hex($d));
868 if(new NetAddr::IP($remote_ip)->within(new NetAddr::IP($destination, $mask))) {
869 # destination matches route, save mac and exit
870 $result= &get_ip($Iface);
871 last;
872 }
873 }
874 }
875 } else {
876 daemon_log("get_local_ip_for_remote_ip was called with a non-ip parameter: $remote_ip", 1);
877 }
878 return $result;
879 }
882 sub send_msg_to_target {
883 my ($msg, $address, $encrypt_key, $msg_header, $session_id) = @_ ;
884 my $error = 0;
885 my $header;
886 my $new_status;
887 my $act_status;
888 my ($sql_statement, $res);
890 if( $msg_header ) {
891 $header = "'$msg_header'-";
892 } else {
893 $header = "";
894 }
896 # Patch the source ip
897 if($msg =~ /<source>0\.0\.0\.0:\d*?<\/source>/) {
898 my $remote_ip = &get_local_ip_for_remote_ip(sprintf("%s", $address =~ /^([0-9\.]*?):.*$/));
899 $msg =~ s/<source>(0\.0\.0\.0):(\d*?)<\/source>/<source>$remote_ip:$2<\/source>/s;
900 }
902 # encrypt xml msg
903 my $crypted_msg = &encrypt_msg($msg, $encrypt_key);
905 # opensocket
906 my $socket = &open_socket($address);
907 if( !$socket ) {
908 daemon_log("$session_id ERROR: cannot send ".$header."msg to $address , host not reachable", 1);
909 $error++;
910 }
912 if( $error == 0 ) {
913 # send xml msg
914 print $socket $crypted_msg."\n";
916 daemon_log("$session_id INFO: send ".$header."msg to $address", 5);
917 daemon_log("DEBUG: message:\n$msg", 9);
919 }
921 # close socket in any case
922 if( $socket ) {
923 close $socket;
924 }
926 if( $error > 0 ) { $new_status = "down"; }
927 else { $new_status = $msg_header; }
930 # known_clients
931 $sql_statement = "SELECT * FROM known_clients WHERE hostname='$address'";
932 $res = $known_clients_db->select_dbentry($sql_statement);
933 if( keys(%$res) > 0) {
934 $act_status = $res->{1}->{'status'};
935 if ($act_status eq "down" && $new_status eq "down") {
936 $sql_statement = "DELETE FROM known_clients WHERE hostname='$address'";
937 $res = $known_clients_db->del_dbentry($sql_statement);
938 daemon_log("$session_id WARNING: failed 2x to send msg to host '$address', delete host from known_clients", 3);
939 } else {
940 $sql_statement = "UPDATE known_clients SET status='$new_status' WHERE hostname='$address'";
941 $res = $known_clients_db->update_dbentry($sql_statement);
942 if($new_status eq "down"){
943 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
944 } else {
945 daemon_log("$session_id INFO: set '$address' from status '$act_status' to '$new_status'", 5);
946 }
947 }
948 }
950 # known_server
951 $sql_statement = "SELECT * FROM $known_server_tn WHERE hostname='$address'";
952 $res = $known_server_db->select_dbentry($sql_statement);
953 print STDERR Dumper($res);
954 if( keys(%$res) > 0 ) {
955 $act_status = $res->{1}->{'status'};
956 if ($act_status eq "down" && $new_status eq "down") {
957 $sql_statement = "DELETE FROM known_server WHERE hostname='$address'";
958 $res = $known_server_db->del_dbentry($sql_statement);
959 daemon_log("$session_id WARNING: failed 2x to send a message to host '$address', delete host from known_server", 3);
960 }
961 else {
962 $sql_statement = "UPDATE known_server SET status='$new_status' WHERE hostname='$address'";
963 $res = $known_server_db->update_dbentry($sql_statement);
964 if($new_status eq "down"){
965 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
966 }
967 else {
968 daemon_log("$session_id INFO: set '$address' from status '$act_status' to '$new_status'", 5);
969 }
970 }
971 }
972 return $error;
973 }
976 sub update_jobdb_status_for_send_msgs {
977 my ($answer, $error) = @_;
978 if( $answer =~ /<jobdb_id>(\d+)<\/jobdb_id>/ ) {
979 my $jobdb_id = $1;
981 # sending msg faild
982 if( $error ) {
983 if (not $answer =~ /<header>trigger_action_reinstall<\/header>/) {
984 my $sql_statement = "UPDATE $job_queue_tn ".
985 "SET status='error', result='can not deliver msg, please consult log file' ".
986 "WHERE id=$jobdb_id";
987 my $res = $job_db->update_dbentry($sql_statement);
988 }
990 # sending msg was successful
991 } else {
992 my $sql_statement = "UPDATE $job_queue_tn ".
993 "SET status='done' ".
994 "WHERE id=$jobdb_id AND status='processed'";
995 my $res = $job_db->update_dbentry($sql_statement);
996 }
997 }
998 }
1000 sub _start {
1001 my ($kernel) = $_[KERNEL];
1002 &trigger_db_loop($kernel);
1003 $global_kernel = $kernel;
1004 $kernel->yield('register_at_foreign_servers');
1005 $kernel->yield('create_fai_server_db', $fai_server_tn );
1006 $kernel->yield('create_fai_release_db', $fai_release_tn );
1007 $kernel->sig(USR1 => "sig_handler");
1008 $kernel->sig(USR2 => "create_packages_list_db");
1009 }
1011 sub sig_handler {
1012 my ($kernel, $signal) = @_[KERNEL, ARG0] ;
1013 daemon_log("0 INFO got signal '$signal'", 1);
1014 $kernel->sig_handled();
1015 return;
1016 }
1019 sub msg_to_decrypt {
1020 my ($session, $heap) = @_[SESSION, HEAP];
1021 my $session_id = $session->ID;
1022 my ($msg, $msg_hash, $module);
1023 my $error = 0;
1025 # hole neue msg aus @msgs_to_decrypt
1026 my $next_msg = shift @msgs_to_decrypt;
1028 # entschlüssle sie
1030 # msg is from a new client or gosa
1031 ($msg, $msg_hash, $module) = &input_from_unknown_host($next_msg, $session_id);
1032 # msg is from a gosa-si-server or gosa-si-bus
1033 if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1034 ($msg, $msg_hash, $module) = &input_from_known_server($next_msg, $heap->{'remote_ip'}, $session_id);
1035 }
1036 # msg is from a gosa-si-client
1037 if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1038 ($msg, $msg_hash, $module) = &input_from_known_client($next_msg, $heap->{'remote_ip'}, $session_id);
1039 }
1040 # an error occurred
1041 if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1042 # if an incoming msg could not be decrypted (maybe a wrong key), send client a ping. If the client
1043 # could not understand a msg from its server the client cause a re-registering process
1044 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);
1045 my $sql_statement = "SELECT * FROM $main::known_clients_tn WHERE (hostname LIKE '".$heap->{'remote_ip'}."%')";
1046 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1047 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1048 my $host_name = $hit->{'hostname'};
1049 my $host_key = $hit->{'hostkey'};
1050 my $ping_msg = "<xml> <header>gosa_ping</header> <source>$server_address</source> <target>$host_name</target></xml>";
1051 my $error = &send_msg_to_target($ping_msg, $host_name, $host_key, "gosa_ping", $session_id);
1052 &update_jobdb_status_for_send_msgs($ping_msg, $error);
1053 }
1054 $error++;
1055 }
1057 # add message to incoming_db
1058 if( $error == 0) {
1059 my $header = @{$msg_hash->{'header'}}[0];
1060 my $target = @{$msg_hash->{'target'}}[0];
1061 my $res = $incoming_db->add_dbentry( {table=>$incoming_tn,
1062 primkey=>[],
1063 headertag=>$header,
1064 targettag=>$target,
1065 xmlmessage=>$msg,
1066 timestamp=>&get_time,
1067 module=>$module,
1068 } );
1069 if ($res != 0) {
1070 # TODO ist das mit $! so ok???
1071 #&daemon_log("$session_id ERROR: cannot add message to incoming.db: $!", 1);
1072 }
1073 }
1075 }
1078 sub next_task {
1079 my ($session, $heap) = @_[SESSION, HEAP];
1080 my $task = POE::Wheel::Run->new(
1081 Program => sub { process_task($session, $heap) },
1082 StdioFilter => POE::Filter::Reference->new(),
1083 StdoutEvent => "task_result",
1084 StderrEvent => "task_debug",
1085 CloseEvent => "task_done",
1086 );
1088 $heap->{task}->{ $task->ID } = $task;
1089 }
1091 sub handle_task_result {
1092 my ($kernel, $heap, $result) = @_[KERNEL, HEAP, ARG0];
1093 my $client_answer = $result->{'answer'};
1094 if( $client_answer =~ s/session_id=(\d+)$// ) {
1095 my $session_id = $1;
1096 if( defined $session_id ) {
1097 my $session_reference = $kernel->ID_id_to_session($session_id);
1098 if( defined $session_reference ) {
1099 $heap = $session_reference->get_heap();
1100 }
1101 }
1103 if(exists $heap->{'client'}) {
1104 $heap->{'client'}->put($client_answer);
1105 }
1106 }
1107 $kernel->sig(CHLD => "child_reap");
1108 }
1110 sub handle_task_debug {
1111 my $result = $_[ARG0];
1112 print STDERR "$result\n";
1113 }
1115 sub handle_task_done {
1116 my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
1117 delete $heap->{task}->{$task_id};
1118 }
1120 sub process_task {
1121 no strict "refs";
1122 my ($session, $heap, $input) = @_;
1123 my $session_id = $session->ID;
1124 my $error = 0;
1125 my $answer_l;
1126 my ($answer_header, @answer_target_l, $answer_source);
1127 my $client_answer = "";
1129 ##################################################
1130 # fetch first unprocessed message from incoming_db
1131 # sometimes the program is faster than sqlite, so wait until informations are present at db
1132 my $id_sql;
1133 my $id_res;
1134 my $message_id;
1135 # TODO : das hier ist sehr sehr unschön
1136 # to be tested: speed enhancement with usleep 100000???
1137 while (1) {
1138 $id_sql = "SELECT min(id) FROM $incoming_tn WHERE (NOT headertag LIKE 'answer%')";
1139 $id_res = $incoming_db->exec_statement($id_sql);
1140 $message_id = @{@$id_res[0]}[0];
1141 if (defined $message_id) { last }
1142 usleep(100000);
1143 }
1145 # fetch new message from incoming_db
1146 my $sql = "SELECT * FROM $incoming_tn WHERE id=$message_id";
1147 my $res = $incoming_db->exec_statement($sql);
1149 # prepare all variables needed to process message
1150 my $msg = @{@$res[0]}[4];
1151 my $incoming_id = @{@$res[0]}[0];
1152 my $module = @{@$res[0]}[5];
1153 my $header = @{@$res[0]}[2];
1154 my $msg_hash = $xml->XMLin($msg, ForceArray=>1);
1156 # messages which are an answer to a still running process should not be processed here
1157 if ($header =~ /^answer_(\d+)/) {
1158 return;
1159 }
1161 # delete message from db
1162 my $delete_sql = "DELETE FROM $incoming_tn WHERE id=$incoming_id";
1163 my $delete_res = $incoming_db->exec_statement($delete_sql);
1165 ######################
1166 # process incoming msg
1167 if( $error == 0) {
1168 daemon_log("$session_id INFO: Incoming msg (session_id=$session_id) with header '".@{$msg_hash->{'header'}}[0].
1169 "' from '".$heap->{'remote_ip'}."'", 5);
1170 daemon_log("$session_id DEBUG: Processing module ".$module, 7);
1171 $answer_l = &{ $module."::process_incoming_msg" }($msg, $msg_hash, $session_id);
1173 if ( 0 < @{$answer_l} ) {
1174 my $answer_str = join("\n", @{$answer_l});
1175 while ($answer_str =~ /<header>(\w+)<\/header>/g) {
1176 daemon_log("$session_id INFO: got answer message with header '$1'", 5);
1177 }
1178 daemon_log("$session_id DEBUG: $module: got answer from module: \n".$answer_str,8);
1179 } else {
1180 daemon_log("$session_id DEBUG: $module: got no answer from module!" ,8);
1181 }
1183 }
1184 if( !$answer_l ) { $error++ };
1186 ########
1187 # answer
1188 if( $error == 0 ) {
1190 foreach my $answer ( @{$answer_l} ) {
1191 # check outgoing msg to xml validity
1192 my $answer_hash = &check_outgoing_xml_validity($answer);
1193 if( not defined $answer_hash ) { next; }
1195 $answer_header = @{$answer_hash->{'header'}}[0];
1196 @answer_target_l = @{$answer_hash->{'target'}};
1197 $answer_source = @{$answer_hash->{'source'}}[0];
1199 # deliver msg to all targets
1200 foreach my $answer_target ( @answer_target_l ) {
1202 # targets of msg are all gosa-si-clients in known_clients_db
1203 if( $answer_target eq "*" ) {
1204 # answer is for all clients
1205 my $sql_statement= "SELECT * FROM known_clients";
1206 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1207 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1208 my $host_name = $hit->{hostname};
1209 my $host_key = $hit->{hostkey};
1210 my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1211 &update_jobdb_status_for_send_msgs($answer, $error);
1212 }
1213 }
1215 # targets of msg are all gosa-si-server in known_server_db
1216 elsif( $answer_target eq "KNOWN_SERVER" ) {
1217 # answer is for all server in known_server
1218 my $sql_statement= "SELECT * FROM known_server";
1219 my $query_res = $known_server_db->select_dbentry( $sql_statement );
1220 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1221 my $host_name = $hit->{hostname};
1222 my $host_key = $hit->{hostkey};
1223 $answer =~ s/KNOWN_SERVER/$host_name/g;
1224 my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1225 &update_jobdb_status_for_send_msgs($answer, $error);
1226 }
1227 }
1229 # target of msg is GOsa
1230 elsif( $answer_target eq "GOSA" ) {
1231 my $session_id = ($1) if $answer =~ /<session_id>(\d+?)<\/session_id>/;
1232 my $add_on = "";
1233 if( defined $session_id ) {
1234 $add_on = ".session_id=$session_id";
1235 }
1236 # answer is for GOSA and has to returned to connected client
1237 my $gosa_answer = &encrypt_msg($answer, $GosaPackages_key);
1238 $client_answer = $gosa_answer.$add_on;
1239 }
1241 # target of msg is job queue at this host
1242 elsif( $answer_target eq "JOBDB") {
1243 $answer =~ /<header>(\S+)<\/header>/;
1244 my $header;
1245 if( defined $1 ) { $header = $1; }
1246 my $error = &send_msg_to_target($answer, $server_address, $GosaPackages_key, $header, $session_id);
1247 &update_jobdb_status_for_send_msgs($answer, $error);
1248 }
1250 # target of msg is a mac address
1251 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 ) {
1252 daemon_log("$session_id INFO: target is mac address '$answer_target', looking for host in known_clients", 5);
1253 my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$answer_target'";
1254 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1255 my $found_ip_flag = 0;
1256 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1257 my $host_name = $hit->{hostname};
1258 my $host_key = $hit->{hostkey};
1259 $answer =~ s/$answer_target/$host_name/g;
1260 daemon_log("$session_id INFO: found host '$host_name', associated to '$answer_target'", 5);
1261 my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1262 &update_jobdb_status_for_send_msgs($answer, $error);
1263 $found_ip_flag++ ;
1264 }
1265 if( $found_ip_flag == 0) {
1266 daemon_log("$session_id WARNING: no host found in known_clients with mac address '$answer_target'", 3);
1267 if( $bus_activ eq "true" ) {
1268 daemon_log("$session_id INFO: try to forward msg '$answer_header' to bus '$bus_address'", 5);
1269 my $sql_statement = "SELECT * FROM known_server WHERE hostname='$bus_address'";
1270 my $query_res = $known_server_db->select_dbentry( $sql_statement );
1271 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1272 my $bus_address = $hit->{hostname};
1273 my $bus_key = $hit->{hostkey};
1274 my $error = &send_msg_to_target($answer, $bus_address, $bus_key, $answer_header, $session_id);
1275 &update_jobdb_status_for_send_msgs($answer, $error);
1276 last;
1277 }
1278 }
1280 }
1282 # answer is for one specific host
1283 } else {
1284 # get encrypt_key
1285 my $encrypt_key = &get_encrypt_key($answer_target);
1286 if( not defined $encrypt_key ) {
1287 # unknown target, forward msg to bus
1288 daemon_log("$session_id WARNING: unknown target '$answer_target'", 3);
1289 if( $bus_activ eq "true" ) {
1290 daemon_log("$session_id INFO: try to forward msg '$answer_header' to bus '$bus_address'", 5);
1291 my $sql_statement = "SELECT * FROM known_server WHERE hostname='$bus_address'";
1292 my $query_res = $known_server_db->select_dbentry( $sql_statement );
1293 my $res_length = keys( %{$query_res} );
1294 if( $res_length == 0 ){
1295 daemon_log("$session_id WARNING: send '$answer_header' to '$bus_address' failed, ".
1296 "no bus found in known_server", 3);
1297 }
1298 else {
1299 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1300 my $bus_key = $hit->{hostkey};
1301 my $error = &send_msg_to_target($answer, $bus_address, $bus_key, $answer_header,$session_id );
1302 &update_jobdb_status_for_send_msgs($answer, $error);
1303 }
1304 }
1305 }
1306 next;
1307 }
1308 my $error = &send_msg_to_target($answer, $answer_target, $encrypt_key, $answer_header,$session_id);
1309 &update_jobdb_status_for_send_msgs($answer, $error);
1310 }
1311 }
1312 }
1313 }
1315 my $filter = POE::Filter::Reference->new();
1316 my %result = (
1317 status => "seems ok to me",
1318 answer => $client_answer,
1319 );
1321 my $output = $filter->put( [ \%result ] );
1322 print @$output;
1325 }
1328 sub trigger_db_loop {
1329 my ($kernel) = @_ ;
1330 $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1331 $kernel->delay_set('watch_for_done_jobs', $job_queue_loop_delay);
1332 $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay);
1333 $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay);
1334 $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay);
1335 }
1338 sub watch_for_done_jobs {
1339 my ($kernel,$heap) = @_[KERNEL, HEAP];
1341 my $sql_statement = "SELECT * FROM ".$job_queue_tn.
1342 " WHERE status='done'";
1343 my $res = $job_db->select_dbentry( $sql_statement );
1345 while( my ($id, $hit) = each %{$res} ) {
1346 my $jobdb_id = $hit->{id};
1347 my $sql_statement = "DELETE FROM $job_queue_tn WHERE id=$jobdb_id";
1348 my $res = $job_db->del_dbentry($sql_statement);
1349 }
1351 $kernel->delay_set('watch_for_done_jobs',$job_queue_loop_delay);
1352 }
1355 sub watch_for_new_jobs {
1356 if($watch_for_new_jobs_in_progress == 0) {
1357 $watch_for_new_jobs_in_progress = 1;
1358 my ($kernel,$heap) = @_[KERNEL, HEAP];
1360 # check gosa job queue for jobs with executable timestamp
1361 my $timestamp = &get_time();
1362 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE status='waiting' AND (CAST (timestamp AS INTEGER)) < $timestamp ORDER BY timestamp";
1363 my $res = $job_db->exec_statement( $sql_statement );
1365 # Merge all new jobs that would do the same actions
1366 my @drops;
1367 my $hits;
1368 foreach my $hit (reverse @{$res} ) {
1369 my $macaddress= lc @{$hit}[8];
1370 my $headertag= @{$hit}[5];
1371 if(
1372 defined($hits->{$macaddress}) &&
1373 defined($hits->{$macaddress}->{$headertag}) &&
1374 defined($hits->{$macaddress}->{$headertag}[0])
1375 ) {
1376 push @drops, "DELETE FROM $job_queue_tn WHERE id = $hits->{$macaddress}->{$headertag}[0]";
1377 }
1378 $hits->{$macaddress}->{$headertag}= $hit;
1379 }
1381 # Delete new jobs with a matching job in state 'processing'
1382 foreach my $macaddress (keys %{$hits}) {
1383 foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1384 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1385 if(defined($jobdb_id)) {
1386 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND headertag='$jobdb_headertag' AND status='processing'";
1387 my $res = $job_db->exec_statement( $sql_statement );
1388 foreach my $hit (@{$res}) {
1389 push @drops, "DELETE FROM $job_queue_tn WHERE id=$jobdb_id";
1390 }
1391 } else {
1392 daemon_log("J ERROR: Job without id exists for macaddress $macaddress!", 1);
1393 }
1394 }
1395 }
1397 # Commit deletion
1398 $job_db->exec_statementlist(\@drops);
1400 # Look for new jobs that could be executed
1401 foreach my $macaddress (keys %{$hits}) {
1403 # Look if there is an executing job
1404 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND status='processing'";
1405 my $res = $job_db->exec_statement( $sql_statement );
1407 # Skip new jobs for host if there is a processing job
1408 if(defined($res) and defined @{$res}[0]) {
1409 next;
1410 }
1412 foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1413 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1414 if(defined($jobdb_id)) {
1415 my $job_msg = @{$hits->{$macaddress}->{$jobdb_headertag}}[7];
1417 daemon_log("J DEBUG: its time to execute $job_msg", 7);
1418 my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$macaddress'";
1419 my $res_hash = $known_clients_db->select_dbentry( $sql_statement );
1421 # expect macaddress is unique!!!!!!
1422 my $target = $res_hash->{1}->{hostname};
1424 # change header
1425 $job_msg =~ s/<header>job_/<header>gosa_/;
1427 # add sqlite_id
1428 $job_msg =~ s/<\/xml>$/<jobdb_id>$jobdb_id<\/jobdb_id><\/xml>/;
1430 $job_msg =~ /<header>(\S+)<\/header>/;
1431 my $header = $1 ;
1432 my $func_error = &send_msg_to_target($job_msg, $server_address, $GosaPackages_key, $header, "J");
1434 # update status in job queue to 'processing'
1435 $sql_statement = "UPDATE $job_queue_tn SET status='processing' WHERE id=$jobdb_id";
1436 my $res = $job_db->update_dbentry($sql_statement);
1437 # TODO: abfangen ob alles in ordnung ist oder nicht, wenn nicht error schmeißen
1439 # We don't want parallel processing
1440 last;
1441 }
1442 }
1443 }
1445 $watch_for_new_jobs_in_progress = 0;
1446 $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1447 }
1448 }
1451 sub watch_for_new_messages {
1452 my ($kernel,$heap) = @_[KERNEL, HEAP];
1453 my @coll_user_msg; # collection list of outgoing messages
1455 # check messaging_db for new incoming messages with executable timestamp
1456 my $timestamp = &get_time();
1457 my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( (CAST(timestamp AS INTEGER))<$timestamp AND flag='n' AND direction='in' )";
1458 my $res = $messaging_db->exec_statement( $sql_statement );
1459 foreach my $hit (@{$res}) {
1461 # create outgoing messages
1462 my $message_to = @{$hit}[3];
1463 # translate message_to to plain login name
1464 my @message_to_l = split(/,/, $message_to);
1465 my %receiver_h;
1466 foreach my $receiver (@message_to_l) {
1467 if ($receiver =~ /^u_([\s\S]*)$/) {
1468 $receiver_h{$1} = 0;
1469 } elsif ($receiver =~ /^g_([\s\S]*)$/) {
1470 my $group_name = $1;
1471 # fetch all group members from ldap and add them to receiver hash
1472 my $ldap_handle = &get_ldap_handle();
1473 if (defined $ldap_handle) {
1474 my $mesg = $ldap_handle->search(
1475 base => $ldap_base,
1476 scope => 'sub',
1477 attrs => ['memberUid'],
1478 filter => "cn=$group_name",
1479 );
1480 if ($mesg->count) {
1481 my @entries = $mesg->entries;
1482 foreach my $entry (@entries) {
1483 my @receivers= $entry->get_value("memberUid");
1484 foreach my $receiver (@receivers) {
1485 $receiver_h{$1} = 0;
1486 }
1487 }
1488 }
1489 # translating errors ?
1490 if ($mesg->code) {
1491 daemon_log("M ERROR: unable to translate group '$group_name' to user list for message delivery: $mesg->error", 1);
1492 }
1493 # ldap handle error ?
1494 } else {
1495 daemon_log("M ERROR: unable to translate group '$group_name' to user list for message delivery: no ldap handle available", 1);
1496 }
1497 } else {
1498 my $sbjct = &encode_base64(@{$hit}[1]);
1499 my $msg = &encode_base64(@{$hit}[7]);
1500 &daemon_log("M WARNING: unknown receiver '$receiver' for a user-message '$sbjct - $msg'", 3);
1501 }
1502 }
1503 my @receiver_l = keys(%receiver_h);
1505 my $message_id = @{$hit}[0];
1507 #add each outgoing msg to messaging_db
1508 my $receiver;
1509 foreach $receiver (@receiver_l) {
1510 my $sql_statement = "INSERT INTO $messaging_tn (id, subject, message_from, message_to, flag, direction, delivery_time, message, timestamp) ".
1511 "VALUES ('".
1512 $message_id."', '". # id
1513 @{$hit}[1]."', '". # subject
1514 @{$hit}[2]."', '". # message_from
1515 $receiver."', '". # message_to
1516 "none"."', '". # flag
1517 "out"."', '". # direction
1518 @{$hit}[6]."', '". # delivery_time
1519 @{$hit}[7]."', '". # message
1520 $timestamp."'". # timestamp
1521 ")";
1522 &daemon_log("M DEBUG: $sql_statement", 1);
1523 my $res = $messaging_db->exec_statement($sql_statement);
1524 &daemon_log("M INFO: message '".@{$hit}[0]."' is prepared for delivery to receiver '$receiver'", 5);
1525 }
1527 # set incoming message to flag d=deliverd
1528 $sql_statement = "UPDATE $messaging_tn SET flag='p' WHERE id='$message_id'";
1529 &daemon_log("M DEBUG: $sql_statement", 7);
1530 $res = $messaging_db->update_dbentry($sql_statement);
1531 &daemon_log("M INFO: message '$message_id' is set to flag 'p' (processed)", 5);
1532 }
1534 $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay);
1535 return;
1536 }
1538 sub watch_for_delivery_messages {
1539 my ($kernel, $heap) = @_[KERNEL, HEAP];
1541 # select outgoing messages
1542 my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( flag='p' AND direction='out' )";
1543 #&daemon_log("0 DEBUG: $sql", 7);
1544 my $res = $messaging_db->exec_statement( $sql_statement );
1546 # build out msg for each usr
1547 foreach my $hit (@{$res}) {
1548 my $receiver = @{$hit}[3];
1549 my $msg_id = @{$hit}[0];
1550 my $subject = @{$hit}[1];
1551 my $message = @{$hit}[7];
1553 # resolve usr -> host where usr is logged in
1554 my $sql = "SELECT * FROM $login_users_tn WHERE (user='$receiver')";
1555 #&daemon_log("0 DEBUG: $sql", 7);
1556 my $res = $login_users_db->exec_statement($sql);
1558 # reciver is logged in nowhere
1559 if (not ref(@$res[0]) eq "ARRAY") { next; }
1561 my $send_succeed = 0;
1562 foreach my $hit (@$res) {
1563 my $receiver_host = @$hit[0];
1564 &daemon_log("M DEBUG: user '$receiver' is logged in at host '$receiver_host'", 7);
1566 # fetch key to encrypt msg propperly for usr/host
1567 my $sql = "SELECT * FROM $known_clients_tn WHERE (hostname='$receiver_host')";
1568 &daemon_log("0 DEBUG: $sql", 7);
1569 my $res = $known_clients_db->exec_statement($sql);
1571 # host is already down
1572 if (not ref(@$res[0]) eq "ARRAY") { next; }
1574 # host is on
1575 my $receiver_key = @{@{$res}[0]}[2];
1576 my %data = ('subject' => $subject, 'message' => $message, 'usr' => $receiver);
1577 my $out_msg = &build_msg("usr_msg", $server_address, $receiver_host, \%data );
1578 my $error = &send_msg_to_target($out_msg, $receiver_host, $receiver_key, "usr_msg", 0);
1579 if ($error == 0 ) {
1580 $send_succeed++ ;
1581 }
1582 }
1584 if ($send_succeed) {
1585 # set outgoing msg at db to deliverd
1586 my $sql = "UPDATE $messaging_tn SET flag='d' WHERE (id='$msg_id' AND direction='out' AND message_to='$receiver')";
1587 &daemon_log("0 DEBUG: $sql", 7);
1588 my $res = $messaging_db->exec_statement($sql);
1589 }
1590 }
1592 $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay);
1593 return;
1594 }
1597 sub watch_for_done_messages {
1598 my ($kernel,$heap) = @_[KERNEL, HEAP];
1600 my $sql = "SELECT * FROM $messaging_tn WHERE (flag='p' AND direction='in')";
1601 #&daemon_log("0 DEBUG: $sql", 7);
1602 my $res = $messaging_db->exec_statement($sql);
1604 foreach my $hit (@{$res}) {
1605 my $msg_id = @{$hit}[0];
1607 my $sql = "SELECT * FROM $messaging_tn WHERE (id='$msg_id' AND direction='out' AND (NOT flag='s'))";
1608 #&daemon_log("0 DEBUG: $sql", 7);
1609 my $res = $messaging_db->exec_statement($sql);
1611 # not all usr msgs have been seen till now
1612 if ( ref(@$res[0]) eq "ARRAY") { next; }
1614 $sql = "DELETE FROM $messaging_tn WHERE (id='$msg_id')";
1615 #&daemon_log("0 DEBUG: $sql", 7);
1616 $res = $messaging_db->exec_statement($sql);
1618 }
1620 $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay);
1621 return;
1622 }
1625 sub get_ldap_handle {
1626 my ($session_id) = @_;
1627 my $heap;
1628 my $ldap_handle;
1630 if (not defined $session_id ) { $session_id = 0 };
1631 if ($session_id =~ /[^0-9]*/) { $session_id = 0 };
1633 if ($session_id == 0) {
1634 daemon_log("$session_id DEBUG: get_ldap_handle invoked without a session_id, create a new ldap_handle", 7);
1635 $ldap_handle = Net::LDAP->new( $ldap_uri );
1636 $ldap_handle->bind($ldap_admin_dn, password => $ldap_admin_password);
1638 } else {
1639 my $session_reference = $global_kernel->ID_id_to_session($session_id);
1640 if( defined $session_reference ) {
1641 $heap = $session_reference->get_heap();
1642 }
1644 if (not defined $heap) {
1645 daemon_log("$session_id DEBUG: cannot get heap for session_id '$session_id'", 7);
1646 return;
1647 }
1649 # TODO: This "if" is nonsense, because it doesn't prove that the
1650 # used handle is still valid - or if we've to reconnect...
1651 #if (not exists $heap->{ldap_handle}) {
1652 $ldap_handle = Net::LDAP->new( $ldap_uri );
1653 $ldap_handle->bind($ldap_admin_dn, password => $ldap_admin_password);
1654 $heap->{ldap_handle} = $ldap_handle;
1655 #}
1656 }
1657 return $ldap_handle;
1658 }
1661 sub change_fai_state {
1662 my ($st, $targets, $session_id) = @_;
1663 $session_id = 0 if not defined $session_id;
1664 # Set FAI state to localboot
1665 my %mapActions= (
1666 reboot => '',
1667 update => 'softupdate',
1668 localboot => 'localboot',
1669 reinstall => 'install',
1670 rescan => '',
1671 wake => '',
1672 memcheck => 'memcheck',
1673 sysinfo => 'sysinfo',
1674 install => 'install',
1675 );
1677 # Return if this is unknown
1678 if (!exists $mapActions{ $st }){
1679 daemon_log("$session_id ERROR: unknown action '$st', can not translate ot FAIstate", 1);
1680 return;
1681 }
1683 my $state= $mapActions{ $st };
1685 my $ldap_handle = &get_ldap_handle($session_id);
1686 if( defined($ldap_handle) ) {
1688 # Build search filter for hosts
1689 my $search= "(&(objectClass=GOhard)";
1690 foreach (@{$targets}){
1691 $search.= "(macAddress=$_)";
1692 }
1693 $search.= ")";
1695 # If there's any host inside of the search string, procress them
1696 if (!($search =~ /macAddress/)){
1697 daemon_log("$session_id ERROR: no macAddress found in filter statement for LDAP search: '$search'", 1);
1698 return;
1699 }
1701 # Perform search for Unit Tag
1702 my $mesg = $ldap_handle->search(
1703 base => $ldap_base,
1704 scope => 'sub',
1705 attrs => ['dn', 'FAIstate', 'objectClass'],
1706 filter => "$search"
1707 );
1709 if ($mesg->count) {
1710 my @entries = $mesg->entries;
1711 foreach my $entry (@entries) {
1712 # Only modify entry if it is not set to '$state'
1713 if ($entry->get_value("FAIstate") ne "$state"){
1714 daemon_log("$session_id INFO: Setting FAIstate to '$state' for ".$entry->dn, 5);
1715 my $result;
1716 my %tmp = map { $_ => 1 } $entry->get_value("objectClass");
1717 if (exists $tmp{'FAIobject'}){
1718 if ($state eq ''){
1719 $result= $ldap_handle->modify($entry->dn, changes => [
1720 delete => [ FAIstate => [] ] ]);
1721 } else {
1722 $result= $ldap_handle->modify($entry->dn, changes => [
1723 replace => [ FAIstate => $state ] ]);
1724 }
1725 } elsif ($state ne ''){
1726 $result= $ldap_handle->modify($entry->dn, changes => [
1727 add => [ objectClass => 'FAIobject' ],
1728 add => [ FAIstate => $state ] ]);
1729 }
1731 # Errors?
1732 if ($result->code){
1733 daemon_log("$session_id Error: Setting FAIstate to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
1734 }
1735 } else {
1736 daemon_log("$session_id DEBUG FAIstate at host '".$entry->dn."' already at state '$st'", 7);
1737 }
1738 }
1739 }
1740 # if no ldap handle defined
1741 } else {
1742 daemon_log("$session_id ERROR: no LDAP handle defined for update FAIstate", 1);
1743 }
1745 }
1748 sub change_goto_state {
1749 my ($st, $targets, $session_id) = @_;
1750 $session_id = 0 if not defined $session_id;
1752 # Switch on or off?
1753 my $state= $st eq 'active' ? 'active': 'locked';
1755 my $ldap_handle = &get_ldap_handle($session_id);
1756 if( defined($ldap_handle) ) {
1758 # Build search filter for hosts
1759 my $search= "(&(objectClass=GOhard)";
1760 foreach (@{$targets}){
1761 $search.= "(macAddress=$_)";
1762 }
1763 $search.= ")";
1765 # If there's any host inside of the search string, procress them
1766 if (!($search =~ /macAddress/)){
1767 return;
1768 }
1770 # Perform search for Unit Tag
1771 my $mesg = $ldap_handle->search(
1772 base => $ldap_base,
1773 scope => 'sub',
1774 attrs => ['dn', 'gotoMode'],
1775 filter => "$search"
1776 );
1778 if ($mesg->count) {
1779 my @entries = $mesg->entries;
1780 foreach my $entry (@entries) {
1782 # Only modify entry if it is not set to '$state'
1783 if ($entry->get_value("gotoMode") ne $state){
1785 daemon_log("$session_id INFO: Setting gotoMode to '$state' for ".$entry->dn, 5);
1786 my $result;
1787 $result= $ldap_handle->modify($entry->dn, changes => [
1788 replace => [ gotoMode => $state ] ]);
1790 # Errors?
1791 if ($result->code){
1792 &daemon_log("$session_id Error: Setting gotoMode to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
1793 }
1795 }
1796 }
1797 }
1799 }
1800 }
1803 sub run_create_fai_server_db {
1804 my ($kernel, $session, $heap, $table_name) = @_[KERNEL, SESSION, HEAP, ARG0];
1805 my $session_id = $session->ID;
1806 my $task = POE::Wheel::Run->new(
1807 Program => sub { &create_fai_server_db($table_name,$kernel, undef, $session_id) },
1808 StdoutEvent => "session_run_result",
1809 StderrEvent => "session_run_debug",
1810 CloseEvent => "session_run_done",
1811 );
1813 $heap->{task}->{ $task->ID } = $task;
1814 return;
1815 }
1818 sub create_fai_server_db {
1819 my ($table_name, $kernel, $dont_create_packages_list, $session_id) = @_;
1820 my $result;
1822 if (not defined $session_id) { $session_id = 0; }
1823 my $ldap_handle = &get_ldap_handle();
1824 if(defined($ldap_handle)) {
1825 daemon_log("$session_id INFO: create_fai_server_db: start", 5);
1826 my $mesg= $ldap_handle->search(
1827 base => $ldap_base,
1828 scope => 'sub',
1829 attrs => ['FAIrepository', 'gosaUnitTag'],
1830 filter => "(&(FAIrepository=*)(objectClass=FAIrepositoryServer))",
1831 );
1832 if($mesg->{'resultCode'} == 0 &&
1833 $mesg->count != 0) {
1834 foreach my $entry (@{$mesg->{entries}}) {
1835 if($entry->exists('FAIrepository')) {
1836 # Add an entry for each Repository configured for server
1837 foreach my $repo(@{$entry->get_value('FAIrepository', asref => 1)}) {
1838 my($tmp_url,$tmp_server,$tmp_release,$tmp_sections) = split(/\|/, $repo);
1839 my $tmp_tag= $entry->get_value('gosaUnitTag') || "";
1840 $result= $fai_server_db->add_dbentry( {
1841 table => $table_name,
1842 primkey => ['server', 'release', 'tag'],
1843 server => $tmp_url,
1844 release => $tmp_release,
1845 sections => $tmp_sections,
1846 tag => (length($tmp_tag)>0)?$tmp_tag:"",
1847 } );
1848 }
1849 }
1850 }
1851 }
1852 daemon_log("$session_id INFO: create_fai_server_db: finished", 5);
1854 # TODO: Find a way to post the 'create_packages_list_db' event
1855 if(not defined($dont_create_packages_list)) {
1856 &create_packages_list_db(undef, undef, $session_id);
1857 }
1858 }
1860 $ldap_handle->disconnect;
1861 return $result;
1862 }
1865 sub run_create_fai_release_db {
1866 my ($session, $heap, $table_name) = @_[SESSION, HEAP, ARG0];
1867 my $session_id = $session->ID;
1868 my $task = POE::Wheel::Run->new(
1869 Program => sub { &create_fai_release_db($table_name, $session_id) },
1870 StdoutEvent => "session_run_result",
1871 StderrEvent => "session_run_debug",
1872 CloseEvent => "session_run_done",
1873 );
1875 $heap->{task}->{ $task->ID } = $task;
1876 return;
1877 }
1880 sub create_fai_release_db {
1881 my ($table_name, $session_id) = @_;
1882 my $result;
1884 # used for logging
1885 if (not defined $session_id) { $session_id = 0; }
1887 my $ldap_handle = &get_ldap_handle();
1888 if(defined($ldap_handle)) {
1889 daemon_log("$session_id INFO: create_fai_release_db: start",5);
1890 my $mesg= $ldap_handle->search(
1891 base => $ldap_base,
1892 scope => 'sub',
1893 attrs => [],
1894 filter => "(&(objectClass=organizationalUnit)(ou=fai))",
1895 );
1896 if($mesg->{'resultCode'} == 0 &&
1897 $mesg->count != 0) {
1898 # Walk through all possible FAI container ou's
1899 my @sql_list;
1900 my $timestamp= &get_time();
1901 foreach my $ou (@{$mesg->{entries}}) {
1902 my $tmp_classes= resolve_fai_classes($ou->dn, $ldap_handle, $session_id);
1903 if(defined($tmp_classes) && ref($tmp_classes) eq 'HASH') {
1904 my @tmp_array=get_fai_release_entries($tmp_classes);
1905 if(@tmp_array) {
1906 foreach my $entry (@tmp_array) {
1907 if(defined($entry) && ref($entry) eq 'HASH') {
1908 my $sql=
1909 "INSERT INTO $table_name "
1910 ."(timestamp, release, class, type, state) VALUES ("
1911 .$timestamp.","
1912 ."'".$entry->{'release'}."',"
1913 ."'".$entry->{'class'}."',"
1914 ."'".$entry->{'type'}."',"
1915 ."'".$entry->{'state'}."')";
1916 push @sql_list, $sql;
1917 }
1918 }
1919 }
1920 }
1921 }
1923 daemon_log("$session_id DEBUG: Inserting ".scalar @sql_list." entries to DB",8);
1924 if(@sql_list) {
1925 unshift @sql_list, "VACUUM";
1926 unshift @sql_list, "DELETE FROM $table_name";
1927 $fai_release_db->exec_statementlist(\@sql_list);
1928 }
1929 daemon_log("$session_id DEBUG: Done with inserting",7);
1930 }
1931 daemon_log("$session_id INFO: create_fai_release_db: finished",5);
1932 }
1933 $ldap_handle->disconnect;
1934 return $result;
1935 }
1937 sub get_fai_types {
1938 my $tmp_classes = shift || return undef;
1939 my @result;
1941 foreach my $type(keys %{$tmp_classes}) {
1942 if(defined($tmp_classes->{$type}[0]) && (!($tmp_classes->{$type}[0] =~ /^.*?removed.*?$/))) {
1943 my $entry = {
1944 type => $type,
1945 state => $tmp_classes->{$type}[0],
1946 };
1947 push @result, $entry;
1948 }
1949 }
1951 return @result;
1952 }
1954 sub get_fai_state {
1955 my $result = "";
1956 my $tmp_classes = shift || return $result;
1958 foreach my $type(keys %{$tmp_classes}) {
1959 if(defined($tmp_classes->{$type}[0])) {
1960 $result = $tmp_classes->{$type}[0];
1962 # State is equal for all types in class
1963 last;
1964 }
1965 }
1967 return $result;
1968 }
1970 sub resolve_fai_classes {
1971 my ($fai_base, $ldap_handle, $session_id) = @_;
1972 if (not defined $session_id) { $session_id = 0; }
1973 my $result;
1974 my @possible_fai_classes= ("FAIscript", "FAIhook", "FAIpartitionTable", "FAItemplate", "FAIvariable", "FAIprofile", "FAIpackageList");
1975 my $fai_filter= "(|(&(objectClass=FAIclass)(|(objectClass=".join(")(objectClass=", @possible_fai_classes).")))(objectClass=FAIbranch))";
1976 my $fai_classes;
1978 daemon_log("$session_id DEBUG: Searching for FAI entries in base $fai_base",7);
1979 my $mesg= $ldap_handle->search(
1980 base => $fai_base,
1981 scope => 'sub',
1982 attrs => ['cn','objectClass','FAIstate'],
1983 filter => $fai_filter,
1984 );
1985 daemon_log("$session_id DEBUG: Found ".$mesg->count()." FAI entries",7);
1987 if($mesg->{'resultCode'} == 0 &&
1988 $mesg->count != 0) {
1989 foreach my $entry (@{$mesg->{entries}}) {
1990 if($entry->exists('cn')) {
1991 my $tmp_dn= $entry->dn();
1993 # Skip classname and ou dn parts for class
1994 my $tmp_release = ($1) if $tmp_dn =~ /^[^,]+,[^,]+,(.*?),$fai_base$/;
1996 # Skip classes without releases
1997 if((!defined($tmp_release)) || length($tmp_release)==0) {
1998 next;
1999 }
2001 my $tmp_cn= $entry->get_value('cn');
2002 my $tmp_state= $entry->get_value('FAIstate');
2004 my $tmp_type;
2005 # Get FAI type
2006 for my $oclass(@{$entry->get_value('objectClass', asref => 1)}) {
2007 if(grep $_ eq $oclass, @possible_fai_classes) {
2008 $tmp_type= $oclass;
2009 last;
2010 }
2011 }
2013 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
2014 # A Subrelease
2015 my @sub_releases = split(/,/, $tmp_release);
2017 # Walk through subreleases and build hash tree
2018 my $hash;
2019 while(my $tmp_sub_release = pop @sub_releases) {
2020 $hash .= "\{'$tmp_sub_release'\}->";
2021 }
2022 eval('push @{$fai_classes->'.$hash.'{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";');
2023 } else {
2024 # A branch, no subrelease
2025 push @{$fai_classes->{$tmp_release}->{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";
2026 }
2027 } elsif (!$entry->exists('cn')) {
2028 my $tmp_dn= $entry->dn();
2029 my $tmp_release = ($1) if $tmp_dn =~ /^(.*?),$fai_base$/;
2031 # Skip classes without releases
2032 if((!defined($tmp_release)) || length($tmp_release)==0) {
2033 next;
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 # Remove the last two characters
2046 chop($hash);
2047 chop($hash);
2049 eval('$fai_classes->'.$hash.'= {}');
2050 } else {
2051 # A branch, no subrelease
2052 if(!exists($fai_classes->{$tmp_release})) {
2053 $fai_classes->{$tmp_release} = {};
2054 }
2055 }
2056 }
2057 }
2059 # The hash is complete, now we can honor the copy-on-write based missing entries
2060 foreach my $release (keys %$fai_classes) {
2061 $result->{$release}= deep_copy(apply_fai_inheritance($fai_classes->{$release}));
2062 }
2063 }
2064 return $result;
2065 }
2067 sub apply_fai_inheritance {
2068 my $fai_classes = shift || return {};
2069 my $tmp_classes;
2071 # Get the classes from the branch
2072 foreach my $class (keys %{$fai_classes}) {
2073 # Skip subreleases
2074 if($class =~ /^ou=.*$/) {
2075 next;
2076 } else {
2077 $tmp_classes->{$class}= deep_copy($fai_classes->{$class});
2078 }
2079 }
2081 # Apply to each subrelease
2082 foreach my $subrelease (keys %{$fai_classes}) {
2083 if($subrelease =~ /ou=/) {
2084 foreach my $tmp_class (keys %{$tmp_classes}) {
2085 if(!exists($fai_classes->{$subrelease}->{$tmp_class})) {
2086 $fai_classes->{$subrelease}->{$tmp_class} =
2087 deep_copy($tmp_classes->{$tmp_class});
2088 } else {
2089 foreach my $type (keys %{$tmp_classes->{$tmp_class}}) {
2090 if(!exists($fai_classes->{$subrelease}->{$tmp_class}->{$type})) {
2091 $fai_classes->{$subrelease}->{$tmp_class}->{$type}=
2092 deep_copy($tmp_classes->{$tmp_class}->{$type});
2093 }
2094 }
2095 }
2096 }
2097 }
2098 }
2100 # Find subreleases in deeper levels
2101 foreach my $subrelease (keys %{$fai_classes}) {
2102 if($subrelease =~ /ou=/) {
2103 foreach my $subsubrelease (keys %{$fai_classes->{$subrelease}}) {
2104 if($subsubrelease =~ /ou=/) {
2105 apply_fai_inheritance($fai_classes->{$subrelease});
2106 }
2107 }
2108 }
2109 }
2111 return $fai_classes;
2112 }
2114 sub get_fai_release_entries {
2115 my $tmp_classes = shift || return;
2116 my $parent = shift || "";
2117 my @result = shift || ();
2119 foreach my $entry (keys %{$tmp_classes}) {
2120 if(defined($entry)) {
2121 if($entry =~ /^ou=.*$/) {
2122 my $release_name = $entry;
2123 $release_name =~ s/ou=//g;
2124 if(length($parent)>0) {
2125 $release_name = $parent."/".$release_name;
2126 }
2127 my @bufentries = get_fai_release_entries($tmp_classes->{$entry}, $release_name, @result);
2128 foreach my $bufentry(@bufentries) {
2129 push @result, $bufentry;
2130 }
2131 } else {
2132 my @types = get_fai_types($tmp_classes->{$entry});
2133 foreach my $type (@types) {
2134 push @result,
2135 {
2136 'class' => $entry,
2137 'type' => $type->{'type'},
2138 'release' => $parent,
2139 'state' => $type->{'state'},
2140 };
2141 }
2142 }
2143 }
2144 }
2146 return @result;
2147 }
2149 sub deep_copy {
2150 my $this = shift;
2151 if (not ref $this) {
2152 $this;
2153 } elsif (ref $this eq "ARRAY") {
2154 [map deep_copy($_), @$this];
2155 } elsif (ref $this eq "HASH") {
2156 +{map { $_ => deep_copy($this->{$_}) } keys %$this};
2157 } else { die "what type is $_?" }
2158 }
2161 sub session_run_result {
2162 my ($kernel, $heap, $client_answer) = @_[KERNEL, HEAP, ARG0];
2163 $kernel->sig(CHLD => "child_reap");
2164 }
2166 sub session_run_debug {
2167 my $result = $_[ARG0];
2168 print STDERR "$result\n";
2169 }
2171 sub session_run_done {
2172 my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
2173 delete $heap->{task}->{$task_id};
2174 }
2177 sub create_sources_list {
2178 my $session_id = shift;
2179 my $ldap_handle = &main::get_ldap_handle;
2180 my $result="/tmp/gosa_si_tmp_sources_list";
2182 # Remove old file
2183 if(stat($result)) {
2184 unlink($result);
2185 &main::daemon_log("$session_id DEBUG: remove an old version of '$result'", 7);
2186 }
2188 my $fh;
2189 open($fh, ">$result");
2190 if (not defined $fh) {
2191 &main::daemon_log("$session_id DEBUG: cannot open '$result' for writing", 7);
2192 return undef;
2193 }
2194 if(defined($main::ldap_server_dn) and length($main::ldap_server_dn) > 0) {
2195 my $mesg=$ldap_handle->search(
2196 base => $main::ldap_server_dn,
2197 scope => 'base',
2198 attrs => 'FAIrepository',
2199 filter => 'objectClass=FAIrepositoryServer'
2200 );
2201 if($mesg->count) {
2202 foreach my $entry(@{$mesg->{'entries'}}) {
2203 foreach my $value(@{$entry->get_value('FAIrepository', asref => 1)}) {
2204 my ($server, $tag, $release, $sections)= split /\|/, $value;
2205 my $line = "deb $server $release";
2206 $sections =~ s/,/ /g;
2207 $line.= " $sections";
2208 print $fh $line."\n";
2209 }
2210 }
2211 }
2212 } else {
2213 if (defined $main::ldap_server_dn){
2214 &main::daemon_log("$session_id ERROR: something wrong with ldap_server_dn '$main::ldap_server_dn', abort create_sources_list", 1);
2215 } else {
2216 &main::daemon_log("$session_id ERROR: no ldap_server_dn found, abort create_sources_list", 1);
2217 }
2218 }
2219 close($fh);
2221 return $result;
2222 }
2225 sub run_create_packages_list_db {
2226 my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
2227 my $session_id = $session->ID;
2229 my $task = POE::Wheel::Run->new(
2230 Priority => +20,
2231 Program => sub {&create_packages_list_db(undef, undef, $session_id)},
2232 StdoutEvent => "session_run_result",
2233 StderrEvent => "session_run_debug",
2234 CloseEvent => "session_run_done",
2235 );
2236 $heap->{task}->{ $task->ID } = $task;
2237 }
2240 sub create_packages_list_db {
2241 my ($ldap_handle, $sources_file, $session_id) = @_;
2243 # it should not be possible to trigger a recreation of packages_list_db
2244 # while packages_list_db is under construction, so set flag packages_list_under_construction
2245 # which is tested befor recreation can be started
2246 if (-r $packages_list_under_construction) {
2247 daemon_log("$session_id WARNING: packages_list_db is right now under construction, please wait until this process is finished", 3);
2248 return;
2249 } else {
2250 daemon_log("$session_id INFO: create_packages_list_db: start", 5);
2251 # set packages_list_under_construction to true
2252 system("touch $packages_list_under_construction");
2253 @packages_list_statements=();
2254 }
2256 if (not defined $session_id) { $session_id = 0; }
2257 if (not defined $ldap_handle) {
2258 $ldap_handle= &get_ldap_handle();
2260 if (not defined $ldap_handle) {
2261 daemon_log("$session_id ERROR: no ldap_handle available to create_packages_list_db", 1);
2262 unlink($packages_list_under_construction);
2263 return;
2264 }
2265 }
2266 if (not defined $sources_file) {
2267 &main::daemon_log("$session_id INFO: no sources_file given for creating packages list so trigger creation of it", 5);
2268 $sources_file = &create_sources_list($session_id);
2269 }
2271 if (not defined $sources_file) {
2272 &main::daemon_log("$session_id ERROR: no sources_file given under '$sources_file', skip create_packages_list_db", 1);
2273 unlink($packages_list_under_construction);
2274 return;
2275 }
2277 my $line;
2279 open(CONFIG, "<$sources_file") or do {
2280 daemon_log( "$session_id ERROR: create_packages_list_db: Failed to open '$sources_file'", 1);
2281 unlink($packages_list_under_construction);
2282 return;
2283 };
2285 # Read lines
2286 while ($line = <CONFIG>){
2287 # Unify
2288 chop($line);
2289 $line =~ s/^\s+//;
2290 $line =~ s/^\s+/ /;
2292 # Strip comments
2293 $line =~ s/#.*$//g;
2295 # Skip empty lines
2296 if ($line =~ /^\s*$/){
2297 next;
2298 }
2300 # Interpret deb line
2301 if ($line =~ /^deb [^\s]+\s[^\s]+\s[^\s]+/){
2302 my( $baseurl, $dist, $sections ) = ($line =~ /^deb\s([^\s]+)\s+([^\s]+)\s+(.*)$/);
2303 my $section;
2304 foreach $section (split(' ', $sections)){
2305 &parse_package_info( $baseurl, $dist, $section, $session_id );
2306 }
2307 }
2308 }
2310 close (CONFIG);
2312 find(\&cleanup_and_extract, keys( %repo_dirs ));
2313 &main::strip_packages_list_statements();
2314 unshift @packages_list_statements, "VACUUM";
2315 $packages_list_db->exec_statementlist(\@packages_list_statements);
2316 unlink($packages_list_under_construction);
2317 daemon_log("$session_id INFO: create_packages_list_db: finished", 5);
2318 return;
2319 }
2321 # This function should do some intensive task to minimize the db-traffic
2322 sub strip_packages_list_statements {
2323 my @existing_entries= @{$packages_list_db->exec_statement("SELECT * FROM $main::packages_list_tn")};
2324 my @new_statement_list=();
2325 my $hash;
2326 my $insert_hash;
2327 my $update_hash;
2328 my $delete_hash;
2329 my $local_timestamp=get_time();
2331 foreach my $existing_entry (@existing_entries) {
2332 $hash->{@{$existing_entry}[0]}->{@{$existing_entry}[1]}->{@{$existing_entry}[2]}= $existing_entry;
2333 }
2335 foreach my $statement (@packages_list_statements) {
2336 if($statement =~ /^INSERT/i) {
2337 # Assign the values from the insert statement
2338 my ($distribution,$package,$version,$section,$description,$template,$timestamp) = ($1,$2,$3,$4,$5,$6,$7) if $statement =~
2339 /^INSERT\s+?INTO\s+?$main::packages_list_tn\s+?VALUES\s*?\('(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)'\s*?\)$/si;
2340 if(exists($hash->{$distribution}->{$package}->{$version})) {
2341 # If section or description has changed, update the DB
2342 if(
2343 (! (@{$hash->{$distribution}->{$package}->{$version}}[3] eq $section)) or
2344 (! (@{$hash->{$distribution}->{$package}->{$version}}[4] eq $description))
2345 ) {
2346 @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,undef);
2347 }
2348 } else {
2349 # Insert a non-existing entry to db
2350 @{$insert_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
2351 }
2352 } elsif ($statement =~ /^UPDATE/i) {
2353 my ($template,$package,$version) = ($1,$2,$3) if $statement =~
2354 /^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;
2355 foreach my $distribution (keys %{$hash}) {
2356 if(exists($insert_hash->{$distribution}->{$package}->{$version})) {
2357 # update the insertion hash to execute only one query per package (insert instead insert+update)
2358 @{$insert_hash->{$distribution}->{$package}->{$version}}[5]= $template;
2359 } elsif(exists($hash->{$distribution}->{$package}->{$version})) {
2360 if( ! (@{$hash->{$distribution}->{$package}->{$version}}[5] eq $template)) {
2361 my $section;
2362 my $description;
2363 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) and
2364 length(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) > 0 ) {
2365 $section= @{$update_hash->{$distribution}->{$package}->{$version}}[3];
2366 }
2367 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
2368 $description= @{$update_hash->{$distribution}->{$package}->{$version}}[4];
2369 }
2370 @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
2371 }
2372 }
2373 }
2374 }
2375 }
2377 # TODO: Check for orphaned entries
2379 # unroll the insert_hash
2380 foreach my $distribution (keys %{$insert_hash}) {
2381 foreach my $package (keys %{$insert_hash->{$distribution}}) {
2382 foreach my $version (keys %{$insert_hash->{$distribution}->{$package}}) {
2383 push @new_statement_list, "INSERT INTO $main::packages_list_tn VALUES ('$distribution','$package','$version',"
2384 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[3]',"
2385 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[4]',"
2386 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[5]',"
2387 ."'$local_timestamp')";
2388 }
2389 }
2390 }
2392 # unroll the update hash
2393 foreach my $distribution (keys %{$update_hash}) {
2394 foreach my $package (keys %{$update_hash->{$distribution}}) {
2395 foreach my $version (keys %{$update_hash->{$distribution}->{$package}}) {
2396 my $set = "";
2397 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3])) {
2398 $set .= "section = '@{$update_hash->{$distribution}->{$package}->{$version}}[3]', ";
2399 }
2400 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
2401 $set .= "description = '@{$update_hash->{$distribution}->{$package}->{$version}}[4]', ";
2402 }
2403 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[5])) {
2404 $set .= "template = '@{$update_hash->{$distribution}->{$package}->{$version}}[5]', ";
2405 }
2406 if(defined($set) and length($set) > 0) {
2407 $set .= "timestamp = '$local_timestamp'";
2408 } else {
2409 next;
2410 }
2411 push @new_statement_list,
2412 "UPDATE $main::packages_list_tn SET $set WHERE"
2413 ." distribution = '$distribution'"
2414 ." AND package = '$package'"
2415 ." AND version = '$version'";
2416 }
2417 }
2418 }
2420 @packages_list_statements = @new_statement_list;
2421 }
2424 sub parse_package_info {
2425 my ($baseurl, $dist, $section, $session_id)= @_;
2426 my ($package);
2427 if (not defined $session_id) { $session_id = 0; }
2428 my ($path) = ($baseurl =~ m%://[^/]*(.*)$%);
2429 $repo_dirs{ "${repo_path}/pool" } = 1;
2431 foreach $package ("Packages.gz"){
2432 daemon_log("$session_id DEBUG: create_packages_list: fetch $baseurl, $dist, $section", 7);
2433 get_package( "$baseurl/dists/$dist/$section/binary-$arch/$package", "$outdir/$dist/$section", $session_id );
2434 parse_package( "$outdir/$dist/$section", $dist, $path, $session_id );
2435 }
2437 }
2440 sub get_package {
2441 my ($url, $dest, $session_id)= @_;
2442 if (not defined $session_id) { $session_id = 0; }
2444 my $tpath = dirname($dest);
2445 -d "$tpath" || mkpath "$tpath";
2447 # This is ugly, but I've no time to take a look at "how it works in perl"
2448 if(0 == system("wget '$url' -O '$dest' 2>/dev/null") ) {
2449 system("gunzip -cd '$dest' > '$dest.in'");
2450 daemon_log("$session_id DEBUG: run command: gunzip -cd '$dest' > '$dest.in'", 5);
2451 unlink($dest);
2452 daemon_log("$session_id DEBUG: delete file '$dest'", 5);
2453 } else {
2454 daemon_log("$session_id ERROR: create_packages_list_db: get_packages: fetching '$url' failed!", 1);
2455 }
2456 return 0;
2457 }
2460 sub parse_package {
2461 my ($path, $dist, $srv_path, $session_id)= @_;
2462 if (not defined $session_id) { $session_id = 0;}
2463 my ($package, $version, $section, $description);
2464 my $PACKAGES;
2465 my $timestamp = &get_time();
2467 if(not stat("$path.in")) {
2468 daemon_log("$session_id ERROR: create_packages_list: parse_package: file '$path.in' is not readable",1);
2469 return;
2470 }
2472 open($PACKAGES, "<$path.in");
2473 if(not defined($PACKAGES)) {
2474 daemon_log("$session_id ERROR: create_packages_list_db: parse_package: cannot open '$path.in'",1);
2475 return;
2476 }
2478 # Read lines
2479 while (<$PACKAGES>){
2480 my $line = $_;
2481 # Unify
2482 chop($line);
2484 # Use empty lines as a trigger
2485 if ($line =~ /^\s*$/){
2486 my $sql = "INSERT INTO packages_list VALUES ('$dist', '$package', '$version', '$section', '$description', '', '$timestamp')";
2487 push(@packages_list_statements, $sql);
2488 $package = "none";
2489 $version = "none";
2490 $section = "none";
2491 $description = "none";
2492 next;
2493 }
2495 # Trigger for package name
2496 if ($line =~ /^Package:\s/){
2497 ($package)= ($line =~ /^Package: (.*)$/);
2498 next;
2499 }
2501 # Trigger for version
2502 if ($line =~ /^Version:\s/){
2503 ($version)= ($line =~ /^Version: (.*)$/);
2504 next;
2505 }
2507 # Trigger for description
2508 if ($line =~ /^Description:\s/){
2509 ($description)= &encode_base64(($line =~ /^Description: (.*)$/));
2510 next;
2511 }
2513 # Trigger for section
2514 if ($line =~ /^Section:\s/){
2515 ($section)= ($line =~ /^Section: (.*)$/);
2516 next;
2517 }
2519 # Trigger for filename
2520 if ($line =~ /^Filename:\s/){
2521 my ($filename) = ($line =~ /^Filename: (.*)$/);
2522 store_fileinfo( $package, $filename, $dist, $srv_path, $version, $repo_path );
2523 next;
2524 }
2525 }
2527 close( $PACKAGES );
2528 unlink( "$path.in" );
2529 &main::daemon_log("$session_id DEBUG: unlink '$path.in'", 1);
2530 }
2533 sub store_fileinfo {
2534 my( $package, $file, $dist, $path, $vers, $srvdir) = @_;
2536 my %fileinfo = (
2537 'package' => $package,
2538 'dist' => $dist,
2539 'version' => $vers,
2540 );
2542 $repo_files{ "${srvdir}/$file" } = \%fileinfo;
2543 }
2546 sub cleanup_and_extract {
2547 my $fileinfo = $repo_files{ $File::Find::name };
2549 if( defined $fileinfo ) {
2551 my $dir = "$outdir/$fileinfo->{ 'dist' }/debconf.d";
2552 my $sql;
2553 my $package = $fileinfo->{ 'package' };
2554 my $newver = $fileinfo->{ 'version' };
2556 mkpath($dir);
2557 system( "dpkg -e '$File::Find::name' '$dir/DEBIAN'" );
2559 if( -f "$dir/DEBIAN/templates" ) {
2561 daemon_log("DEBUG: Found debconf templates in '$package' - $newver", 5);
2563 my $tmpl= "";
2564 {
2565 local $/=undef;
2566 open FILE, "$dir/DEBIAN/templates";
2567 $tmpl = &encode_base64(<FILE>);
2568 close FILE;
2569 }
2570 rmtree("$dir/DEBIAN/templates");
2572 $sql= "update $main::packages_list_tn set template = '$tmpl' where package = '$package' and version = '$newver';";
2573 push @packages_list_statements, $sql;
2574 }
2575 }
2577 return;
2578 }
2581 sub register_at_foreign_servers {
2582 my ($kernel) = $_[KERNEL];
2584 # hole alle bekannten server aus known_server_db
2585 my $sql = "SELECT * FROM $known_server_tn";
2586 my $res = $known_server_db->exec_statement($sql);
2588 # no entries in known_server_db
2589 if (not ref(@$res[0]) eq "ARRAY") {
2590 # TODO
2591 }
2593 foreach my $hit (@$res) {
2594 my $hostname = @$hit[0];
2595 my $hostkey = &create_passwd;
2597 my %data= ('known_clients' => "",
2598 'key' => $hostkey,
2599 );
2600 my $foreign_server_msg = &build_msg('new_server', $server_address, $hostname, \%data);
2601 my $error = &send_msg_to_target($foreign_server_msg, $hostname, $ServerPackages_key, "new_server", 0);
2604 }
2606 $kernel->delay_set("register_at_foreign_servers", $foreign_servers_register_delay);
2607 return;
2608 }
2611 #==== MAIN = main ==============================================================
2612 # parse commandline options
2613 Getopt::Long::Configure( "bundling" );
2614 GetOptions("h|help" => \&usage,
2615 "c|config=s" => \$cfg_file,
2616 "f|foreground" => \$foreground,
2617 "v|verbose+" => \$verbose,
2618 "no-bus+" => \$no_bus,
2619 "no-arp+" => \$no_arp,
2620 );
2622 # read and set config parameters
2623 &check_cmdline_param ;
2624 &read_configfile;
2625 &check_pid;
2627 $SIG{CHLD} = 'IGNORE';
2629 # forward error messages to logfile
2630 if( ! $foreground ) {
2631 open( STDIN, '+>/dev/null' );
2632 open( STDOUT, '+>&STDIN' );
2633 open( STDERR, '+>&STDIN' );
2634 }
2636 # Just fork, if we are not in foreground mode
2637 if( ! $foreground ) {
2638 chdir '/' or die "Can't chdir to /: $!";
2639 $pid = fork;
2640 setsid or die "Can't start a new session: $!";
2641 umask 0;
2642 } else {
2643 $pid = $$;
2644 }
2646 # Do something useful - put our PID into the pid_file
2647 if( 0 != $pid ) {
2648 open( LOCK_FILE, ">$pid_file" );
2649 print LOCK_FILE "$pid\n";
2650 close( LOCK_FILE );
2651 if( !$foreground ) {
2652 exit( 0 )
2653 };
2654 }
2656 # parse head url and revision from svn
2657 my $server_status_hash = { 'developmental'=>'revision', 'stable'=>'release'};
2658 $server_version =~ /^\$HeadURL: (\S+) \$:\$Rev: (\d+) \$$/;
2659 $server_headURL = defined $1 ? $1 : 'unknown' ;
2660 $server_revision = defined $2 ? $2 : 'unknown' ;
2661 if ($server_headURL =~ /\/tag\// ||
2662 $server_headURL =~ /\/branches\// ) {
2663 $server_status = "stable";
2664 } else {
2665 $server_status = "developmental" ;
2666 }
2669 daemon_log(" ", 1);
2670 daemon_log("$0 started!", 1);
2671 daemon_log("status: $server_status", 1);
2672 daemon_log($server_status_hash->{$server_status}.": $server_revision", 1);
2674 if ($no_bus > 0) {
2675 $bus_activ = "false"
2676 }
2678 # connect to incoming_db
2679 unlink($incoming_file_name);
2680 $incoming_db = GOSA::DBsqlite->new($incoming_file_name);
2681 $incoming_db->create_table($incoming_tn, \@incoming_col_names);
2683 # connect to gosa-si job queue
2684 $job_db = GOSA::DBsqlite->new($job_queue_file_name);
2685 $job_db->create_table($job_queue_tn, \@job_queue_col_names);
2687 # connect to known_clients_db
2688 $known_clients_db = GOSA::DBsqlite->new($known_clients_file_name);
2689 $known_clients_db->create_table($known_clients_tn, \@known_clients_col_names);
2691 # connect to known_server_db
2692 unlink($known_server_file_name);
2693 $known_server_db = GOSA::DBsqlite->new($known_server_file_name);
2694 $known_server_db->create_table($known_server_tn, \@known_server_col_names);
2696 # connect to login_usr_db
2697 $login_users_db = GOSA::DBsqlite->new($login_users_file_name);
2698 $login_users_db->create_table($login_users_tn, \@login_users_col_names);
2700 # connect to fai_server_db and fai_release_db
2701 unlink($fai_server_file_name);
2702 $fai_server_db = GOSA::DBsqlite->new($fai_server_file_name);
2703 $fai_server_db->create_table($fai_server_tn, \@fai_server_col_names);
2705 unlink($fai_release_file_name);
2706 $fai_release_db = GOSA::DBsqlite->new($fai_release_file_name);
2707 $fai_release_db->create_table($fai_release_tn, \@fai_release_col_names);
2709 # connect to packages_list_db
2710 #unlink($packages_list_file_name);
2711 unlink($packages_list_under_construction);
2712 $packages_list_db = GOSA::DBsqlite->new($packages_list_file_name);
2713 $packages_list_db->create_table($packages_list_tn, \@packages_list_col_names);
2715 # connect to messaging_db
2716 $messaging_db = GOSA::DBsqlite->new($messaging_file_name);
2717 $messaging_db->create_table($messaging_tn, \@messaging_col_names);
2720 # create xml object used for en/decrypting
2721 $xml = new XML::Simple();
2724 # foreign servers
2725 my @foreign_server_list;
2727 # add foreign server from cfg file
2728 if ($foreign_server_string ne "") {
2729 my @cfg_foreign_server_list = split(",", $foreign_server_string);
2730 foreach my $foreign_server (@cfg_foreign_server_list) {
2731 push(@foreign_server_list, $foreign_server);
2732 }
2733 }
2735 # add foreign server from dns
2736 my @tmp_servers;
2737 if ( !$server_domain) {
2738 # Try our DNS Searchlist
2739 for my $domain(get_dns_domains()) {
2740 chomp($domain);
2741 my @tmp_domains= &get_server_addresses($domain);
2742 if(@tmp_domains) {
2743 for my $tmp_server(@tmp_domains) {
2744 push @tmp_servers, $tmp_server;
2745 }
2746 }
2747 }
2748 if(@tmp_servers && length(@tmp_servers)==0) {
2749 daemon_log("0 WARNING: no foreign gosa-si-server found in DNS for domain '$server_domain'", 3);
2750 }
2751 } else {
2752 @tmp_servers = &get_server_addresses($server_domain);
2753 if( 0 == @tmp_servers ) {
2754 daemon_log("0 WARNING: no foreign gosa-si-server found in DNS for domain '$server_domain'", 3);
2755 }
2756 }
2757 foreach my $server (@tmp_servers) {
2758 unshift(@foreign_server_list, $server);
2759 }
2760 # eliminate duplicate entries
2761 @foreign_server_list = &del_doubles(@foreign_server_list);
2762 my $all_foreign_server = join(", ", @foreign_server_list);
2763 daemon_log("0 INFO: found foreign server in config file and DNS: $all_foreign_server", 5);
2765 # add all found foreign servers to known_server
2766 my $act_timestamp = &get_time();
2767 foreach my $foreign_server (@foreign_server_list) {
2768 my $res = $known_server_db->add_dbentry( {table=>$known_server_tn,
2769 primkey=>['hostname'],
2770 hostname=>$foreign_server,
2771 status=>'not_jet_registered',
2772 hostkey=>"none",
2773 timestamp=>$act_timestamp,
2774 } );
2775 }
2778 POE::Component::Server::TCP->new(
2779 Port => $server_port,
2780 ClientInput => sub {
2781 my ($kernel, $input) = @_[KERNEL, ARG0];
2782 push(@tasks, $input);
2783 push(@msgs_to_decrypt, $input);
2784 $kernel->yield("msg_to_decrypt");
2785 $kernel->yield("next_task");
2786 },
2787 InlineStates => {
2788 next_task => \&next_task,
2789 msg_to_decrypt => \&msg_to_decrypt,
2790 task_result => \&handle_task_result,
2791 task_done => \&handle_task_done,
2792 task_debug => \&handle_task_debug,
2793 child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!" },
2794 }
2795 );
2797 daemon_log("start socket for incoming xml messages at port '$server_port' ", 1);
2799 # create session for repeatedly checking the job queue for jobs
2800 POE::Session->create(
2801 inline_states => {
2802 _start => \&_start,
2803 register_at_foreign_servers => \®ister_at_foreign_servers,
2804 sig_handler => \&sig_handler,
2805 watch_for_new_messages => \&watch_for_new_messages,
2806 watch_for_delivery_messages => \&watch_for_delivery_messages,
2807 watch_for_done_messages => \&watch_for_done_messages,
2808 watch_for_new_jobs => \&watch_for_new_jobs,
2809 watch_for_done_jobs => \&watch_for_done_jobs,
2810 create_packages_list_db => \&run_create_packages_list_db,
2811 create_fai_server_db => \&run_create_fai_server_db,
2812 create_fai_release_db => \&run_create_fai_release_db,
2813 session_run_result => \&session_run_result,
2814 session_run_debug => \&session_run_debug,
2815 session_run_done => \&session_run_done,
2816 child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!" },
2817 }
2818 );
2821 # import all modules
2822 &import_modules;
2824 # TODO
2825 # check wether all modules are gosa-si valid passwd check
2829 POE::Kernel->run();
2830 exit;