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 registered clients at a foreign server
161 our $foreign_clients_db;
162 our $foreign_clients_tn = "foreign_clients";
163 my $foreign_clients_file_name;
164 my @foreign_clients_col_names = ("hostname", "macaddress", "regserver", "timestamp");
166 # holds all logged in user at each client
167 our $login_users_db;
168 our $login_users_tn = "login_users";
169 my $login_users_file_name;
170 my @login_users_col_names = ("client", "user", "timestamp");
172 # holds all fai server, the debian release and tag
173 our $fai_server_db;
174 our $fai_server_tn = "fai_server";
175 my $fai_server_file_name;
176 our @fai_server_col_names = ("timestamp", "server", "release", "sections", "tag");
178 our $fai_release_db;
179 our $fai_release_tn = "fai_release";
180 my $fai_release_file_name;
181 our @fai_release_col_names = ("timestamp", "release", "class", "type", "state");
183 # holds all packages available from different repositories
184 our $packages_list_db;
185 our $packages_list_tn = "packages_list";
186 my $packages_list_file_name;
187 our @packages_list_col_names = ("distribution", "package", "version", "section", "description", "template", "timestamp");
188 my $outdir = "/tmp/packages_list_db";
189 my $arch = "i386";
191 # holds all messages which should be delivered to a user
192 our $messaging_db;
193 our $messaging_tn = "messaging";
194 our @messaging_col_names = ("id INTEGER", "subject", "message_from", "message_to",
195 "flag", "direction", "delivery_time", "message", "timestamp" );
196 my $messaging_file_name;
198 # path to directory to store client install log files
199 our $client_fai_log_dir = "/var/log/fai";
201 # queue which stores taskes until one of the $max_children children are ready to process the task
202 my @tasks = qw();
203 my @msgs_to_decrypt = qw();
204 my $max_children = 2;
207 %cfg_defaults = (
208 "general" => {
209 "log-file" => [\$log_file, "/var/run/".$prg.".log"],
210 "pid-file" => [\$pid_file, "/var/run/".$prg.".pid"],
211 },
212 "bus" => {
213 "activ" => [\$bus_activ, "true"],
214 },
215 "server" => {
216 "port" => [\$server_port, "20081"],
217 "known-clients" => [\$known_clients_file_name, '/var/lib/gosa-si/clients.db' ],
218 "known-servers" => [\$known_server_file_name, '/var/lib/gosa-si/servers.db'],
219 "incoming" => [\$incoming_file_name, '/var/lib/gosa-si/incoming.db'],
220 "login-users" => [\$login_users_file_name, '/var/lib/gosa-si/users.db'],
221 "fai-server" => [\$fai_server_file_name, '/var/lib/gosa-si/fai_server.db'],
222 "fai-release" => [\$fai_release_file_name, '/var/lib/gosa-si/fai_release.db'],
223 "packages-list" => [\$packages_list_file_name, '/var/lib/gosa-si/packages.db'],
224 "messaging" => [\$messaging_file_name, '/var/lib/gosa-si/messaging.db'],
225 "foreign-clients" => [\$foreign_clients_file_name, '/var/lib/gosa-si/foreign_clients.db'],
226 "source-list" => [\$sources_list, '/etc/apt/sources.list'],
227 "repo-path" => [\$repo_path, '/srv/www/repository'],
228 "ldap-uri" => [\$ldap_uri, ""],
229 "ldap-base" => [\$ldap_base, ""],
230 "ldap-admin-dn" => [\$ldap_admin_dn, ""],
231 "ldap-admin-password" => [\$ldap_admin_password, ""],
232 "gosa-unit-tag" => [\$gosa_unit_tag, ""],
233 "max-clients" => [\$max_clients, 10],
234 },
235 "GOsaPackages" => {
236 "ip" => [\$gosa_ip, "0.0.0.0"],
237 "port" => [\$gosa_port, "20082"],
238 "job-queue" => [\$job_queue_file_name, '/var/lib/gosa-si/jobs.db'],
239 "job-queue-loop-delay" => [\$job_queue_loop_delay, 3],
240 "messaging-db-loop-delay" => [\$messaging_db_loop_delay, 3],
241 "key" => [\$GosaPackages_key, "none"],
242 },
243 "ClientPackages" => {
244 "key" => [\$ClientPackages_key, "none"],
245 },
246 "ServerPackages"=> {
247 "address" => [\$foreign_server_string, ""],
248 "domain" => [\$server_domain, ""],
249 "key" => [\$ServerPackages_key, "none"],
250 "key-lifetime" => [\$foreign_servers_register_delay, 600],
251 }
252 );
255 #=== FUNCTION ================================================================
256 # NAME: usage
257 # PARAMETERS: nothing
258 # RETURNS: nothing
259 # DESCRIPTION: print out usage text to STDERR
260 #===============================================================================
261 sub usage {
262 print STDERR << "EOF" ;
263 usage: $prg [-hvf] [-c config]
265 -h : this (help) message
266 -c <file> : config file
267 -f : foreground, process will not be forked to background
268 -v : be verbose (multiple to increase verbosity)
269 -no-bus : starts $prg without connection to bus
270 -no-arp : starts $prg without connection to arp module
272 EOF
273 print "\n" ;
274 }
277 #=== FUNCTION ================================================================
278 # NAME: read_configfile
279 # PARAMETERS: cfg_file - string -
280 # RETURNS: nothing
281 # DESCRIPTION: read cfg_file and set variables
282 #===============================================================================
283 sub read_configfile {
284 my $cfg;
285 if( defined( $cfg_file) && ( (-s $cfg_file) > 0 )) {
286 if( -r $cfg_file ) {
287 $cfg = Config::IniFiles->new( -file => $cfg_file );
288 } else {
289 print STDERR "Couldn't read config file!\n";
290 }
291 } else {
292 $cfg = Config::IniFiles->new() ;
293 }
294 foreach my $section (keys %cfg_defaults) {
295 foreach my $param (keys %{$cfg_defaults{ $section }}) {
296 my $pinfo = $cfg_defaults{ $section }{ $param };
297 ${@$pinfo[ 0 ]} = $cfg->val( $section, $param, @$pinfo[ 1 ] );
298 }
299 }
300 }
303 #=== FUNCTION ================================================================
304 # NAME: logging
305 # PARAMETERS: level - string - default 'info'
306 # msg - string -
307 # facility - string - default 'LOG_DAEMON'
308 # RETURNS: nothing
309 # DESCRIPTION: function for logging
310 #===============================================================================
311 sub daemon_log {
312 # log into log_file
313 my( $msg, $level ) = @_;
314 if(not defined $msg) { return }
315 if(not defined $level) { $level = 1 }
316 if(defined $log_file){
317 open(LOG_HANDLE, ">>$log_file");
318 if(not defined open( LOG_HANDLE, ">>$log_file" )) {
319 print STDERR "cannot open $log_file: $!";
320 return }
321 chomp($msg);
322 $msg =~s/\n//g; # no newlines are allowed in log messages, this is important for later log parsing
323 if($level <= $verbose){
324 my ($seconds, $minutes, $hours, $monthday, $month,
325 $year, $weekday, $yearday, $sommertime) = localtime(time);
326 $hours = $hours < 10 ? $hours = "0".$hours : $hours;
327 $minutes = $minutes < 10 ? $minutes = "0".$minutes : $minutes;
328 $seconds = $seconds < 10 ? $seconds = "0".$seconds : $seconds;
329 my @monthnames = ("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec");
330 $month = $monthnames[$month];
331 $monthday = $monthday < 10 ? $monthday = "0".$monthday : $monthday;
332 $year+=1900;
333 my $name = $prg;
335 my $log_msg = "$month $monthday $hours:$minutes:$seconds $name $msg\n";
336 print LOG_HANDLE $log_msg;
337 if( $foreground ) {
338 print STDERR $log_msg;
339 }
340 }
341 close( LOG_HANDLE );
342 }
343 }
346 #=== FUNCTION ================================================================
347 # NAME: check_cmdline_param
348 # PARAMETERS: nothing
349 # RETURNS: nothing
350 # DESCRIPTION: validates commandline parameter
351 #===============================================================================
352 sub check_cmdline_param () {
353 my $err_config;
354 my $err_counter = 0;
355 if(not defined($cfg_file)) {
356 $cfg_file = "/etc/gosa-si/server.conf";
357 if(! -r $cfg_file) {
358 $err_config = "please specify a config file";
359 $err_counter += 1;
360 }
361 }
362 if( $err_counter > 0 ) {
363 &usage( "", 1 );
364 if( defined( $err_config)) { print STDERR "$err_config\n"}
365 print STDERR "\n";
366 exit( -1 );
367 }
368 }
371 #=== FUNCTION ================================================================
372 # NAME: check_pid
373 # PARAMETERS: nothing
374 # RETURNS: nothing
375 # DESCRIPTION: handels pid processing
376 #===============================================================================
377 sub check_pid {
378 $pid = -1;
379 # Check, if we are already running
380 if( open(LOCK_FILE, "<$pid_file") ) {
381 $pid = <LOCK_FILE>;
382 if( defined $pid ) {
383 chomp( $pid );
384 if( -f "/proc/$pid/stat" ) {
385 my($stat) = `cat /proc/$pid/stat` =~ m/$pid \((.+)\).*/;
386 if( $stat ) {
387 daemon_log("ERROR: Already running",1);
388 close( LOCK_FILE );
389 exit -1;
390 }
391 }
392 }
393 close( LOCK_FILE );
394 unlink( $pid_file );
395 }
397 # create a syslog msg if it is not to possible to open PID file
398 if (not sysopen(LOCK_FILE, $pid_file, O_WRONLY|O_CREAT|O_EXCL, 0644)) {
399 my($msg) = "Couldn't obtain lockfile '$pid_file' ";
400 if (open(LOCK_FILE, '<', $pid_file)
401 && ($pid = <LOCK_FILE>))
402 {
403 chomp($pid);
404 $msg .= "(PID $pid)\n";
405 } else {
406 $msg .= "(unable to read PID)\n";
407 }
408 if( ! ($foreground) ) {
409 openlog( $0, "cons,pid", "daemon" );
410 syslog( "warning", $msg );
411 closelog();
412 }
413 else {
414 print( STDERR " $msg " );
415 }
416 exit( -1 );
417 }
418 }
420 #=== FUNCTION ================================================================
421 # NAME: import_modules
422 # PARAMETERS: module_path - string - abs. path to the directory the modules
423 # are stored
424 # RETURNS: nothing
425 # DESCRIPTION: each file in module_path which ends with '.pm' and activation
426 # state is on is imported by "require 'file';"
427 #===============================================================================
428 sub import_modules {
429 daemon_log(" ", 1);
431 if (not -e $modules_path) {
432 daemon_log("0 ERROR: cannot find directory or directory is not readable: $modules_path", 1);
433 }
435 opendir (DIR, $modules_path) or die "ERROR while loading modules from directory $modules_path : $!\n";
436 while (defined (my $file = readdir (DIR))) {
437 if (not $file =~ /(\S*?).pm$/) {
438 next;
439 }
440 my $mod_name = $1;
442 if( $file =~ /ArpHandler.pm/ ) {
443 if( $no_arp > 0 ) {
444 next;
445 }
446 }
448 eval { require $file; };
449 if ($@) {
450 daemon_log("0 ERROR: gosa-si-server could not load module $file", 1);
451 daemon_log("$@", 5);
452 } else {
453 my $info = eval($mod_name.'::get_module_info()');
454 # Only load module if get_module_info() returns a non-null object
455 if( $info ) {
456 my ($input_address, $input_key, $input, $input_active, $input_type) = @{$info};
457 $known_modules->{$mod_name} = $info;
458 daemon_log("0 INFO: module $mod_name loaded", 5);
459 }
460 }
461 }
462 close (DIR);
463 }
466 #=== FUNCTION ================================================================
467 # NAME: sig_int_handler
468 # PARAMETERS: signal - string - signal arose from system
469 # RETURNS: noting
470 # DESCRIPTION: handels tasks to be done befor signal becomes active
471 #===============================================================================
472 sub sig_int_handler {
473 my ($signal) = @_;
475 # if (defined($ldap_handle)) {
476 # $ldap_handle->disconnect;
477 # }
478 # TODO alle verbliebenden ldap verbindungen aus allen heaps beenden
481 daemon_log("shutting down gosa-si-server", 1);
482 system("kill `ps -C gosa-si-server-nobus -o pid=`");
483 }
484 $SIG{INT} = \&sig_int_handler;
487 sub check_key_and_xml_validity {
488 my ($crypted_msg, $module_key, $session_id) = @_;
489 my $msg;
490 my $msg_hash;
491 my $error_string;
492 eval{
493 $msg = &decrypt_msg($crypted_msg, $module_key);
495 if ($msg =~ /<xml>/i){
496 $msg =~ s/\s+/ /g; # just for better daemon_log
497 daemon_log("$session_id DEBUG: decrypted_msg: \n$msg", 8);
498 $msg_hash = $xml->XMLin($msg, ForceArray=>1);
500 ##############
501 # check header
502 if( not exists $msg_hash->{'header'} ) { die "no header specified"; }
503 my $header_l = $msg_hash->{'header'};
504 if( 1 > @{$header_l} ) { die 'empty header tag'; }
505 if( 1 < @{$header_l} ) { die 'more than one header specified'; }
506 my $header = @{$header_l}[0];
507 if( 0 == length $header) { die 'empty string in header tag'; }
509 ##############
510 # check source
511 if( not exists $msg_hash->{'source'} ) { die "no source specified"; }
512 my $source_l = $msg_hash->{'source'};
513 if( 1 > @{$source_l} ) { die 'empty source tag'; }
514 if( 1 < @{$source_l} ) { die 'more than one source specified'; }
515 my $source = @{$source_l}[0];
516 if( 0 == length $source) { die 'source error'; }
518 ##############
519 # check target
520 if( not exists $msg_hash->{'target'} ) { die "no target specified"; }
521 my $target_l = $msg_hash->{'target'};
522 if( 1 > @{$target_l} ) { die 'empty target tag'; }
523 }
524 };
525 if($@) {
526 daemon_log("$session_id DEBUG: do not understand the message: $@", 7);
527 $msg = undef;
528 $msg_hash = undef;
529 }
531 return ($msg, $msg_hash);
532 }
535 sub check_outgoing_xml_validity {
536 my ($msg) = @_;
538 my $msg_hash;
539 eval{
540 $msg_hash = $xml->XMLin($msg, ForceArray=>1);
542 ##############
543 # check header
544 my $header_l = $msg_hash->{'header'};
545 if( 1 != @{$header_l} ) {
546 die 'no or more than one headers specified';
547 }
548 my $header = @{$header_l}[0];
549 if( 0 == length $header) {
550 die 'header has length 0';
551 }
553 ##############
554 # check source
555 my $source_l = $msg_hash->{'source'};
556 if( 1 != @{$source_l} ) {
557 die 'no or more than 1 sources specified';
558 }
559 my $source = @{$source_l}[0];
560 if( 0 == length $source) {
561 die 'source has length 0';
562 }
563 unless( $source =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
564 $source =~ /^GOSA$/i ) {
565 die "source '$source' is neither a complete ip-address with port nor 'GOSA'";
566 }
568 ##############
569 # check target
570 my $target_l = $msg_hash->{'target'};
571 if( 0 == @{$target_l} ) {
572 die "no targets specified";
573 }
574 foreach my $target (@$target_l) {
575 if( 0 == length $target) {
576 die "target has length 0";
577 }
578 unless( $target =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
579 $target =~ /^GOSA$/i ||
580 $target =~ /^\*$/ ||
581 $target =~ /KNOWN_SERVER/i ||
582 $target =~ /JOBDB/i ||
583 $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 ){
584 die "target '$target' is not a complete ip-address with port or a valid target name or a mac-address";
585 }
586 }
587 };
588 if($@) {
589 daemon_log("WARNING: outgoing msg is not gosa-si envelope conform", 5);
590 daemon_log("$@ ".(defined($msg) && length($msg)>0)?$msg:"Empty Message", 8);
591 $msg_hash = undef;
592 }
594 return ($msg_hash);
595 }
598 sub input_from_known_server {
599 my ($input, $remote_ip, $session_id) = @_ ;
600 my ($msg, $msg_hash, $module);
602 my $sql_statement= "SELECT * FROM known_server";
603 my $query_res = $known_server_db->select_dbentry( $sql_statement );
605 while( my ($hit_num, $hit) = each %{ $query_res } ) {
606 my $host_name = $hit->{hostname};
607 if( not $host_name =~ "^$remote_ip") {
608 next;
609 }
610 my $host_key = $hit->{hostkey};
611 daemon_log("$session_id DEBUG: input_from_known_server: host_name: $host_name", 7);
612 daemon_log("$session_id DEBUG: input_from_known_server: host_key: $host_key", 7);
614 # check if module can open msg envelope with module key
615 my ($tmp_msg, $tmp_msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
616 if( (!$tmp_msg) || (!$tmp_msg_hash) ) {
617 daemon_log("$session_id DEBUG: input_from_known_server: deciphering raise error", 7);
618 daemon_log("$@", 8);
619 next;
620 }
621 else {
622 $msg = $tmp_msg;
623 $msg_hash = $tmp_msg_hash;
624 $module = "ServerPackages";
625 last;
626 }
627 }
629 if( (!$msg) || (!$msg_hash) || (!$module) ) {
630 daemon_log("$session_id DEBUG: Incoming message is not from a known server", 7);
631 }
633 return ($msg, $msg_hash, $module);
634 }
637 sub input_from_known_client {
638 my ($input, $remote_ip, $session_id) = @_ ;
639 my ($msg, $msg_hash, $module);
641 my $sql_statement= "SELECT * FROM known_clients";
642 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
643 while( my ($hit_num, $hit) = each %{ $query_res } ) {
644 my $host_name = $hit->{hostname};
645 if( not $host_name =~ /^$remote_ip:\d*$/) {
646 next;
647 }
648 my $host_key = $hit->{hostkey};
649 &daemon_log("$session_id DEBUG: input_from_known_client: host_name: $host_name", 7);
650 &daemon_log("$session_id DEBUG: input_from_known_client: host_key: $host_key", 7);
652 # check if module can open msg envelope with module key
653 ($msg, $msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
655 if( (!$msg) || (!$msg_hash) ) {
656 &daemon_log("$session_id DEGUG: input_from_known_client: deciphering raise error", 7);
657 &daemon_log("$@", 8);
658 next;
659 }
660 else {
661 $module = "ClientPackages";
662 last;
663 }
664 }
666 if( (!$msg) || (!$msg_hash) || (!$module) ) {
667 &daemon_log("$session_id DEBUG: Incoming message is not from a known client", 7);
668 }
670 return ($msg, $msg_hash, $module);
671 }
674 sub input_from_unknown_host {
675 no strict "refs";
676 my ($input, $session_id) = @_ ;
677 my ($msg, $msg_hash, $module);
678 my $error_string;
680 my %act_modules = %$known_modules;
682 while( my ($mod, $info) = each(%act_modules)) {
684 # check a key exists for this module
685 my $module_key = ${$mod."_key"};
686 if( not defined $module_key ) {
687 if( $mod eq 'ArpHandler' ) {
688 next;
689 }
690 daemon_log("$session_id ERROR: no key specified in config file for $mod", 1);
691 next;
692 }
693 daemon_log("$session_id DEBUG: $mod: $module_key", 7);
695 # check if module can open msg envelope with module key
696 ($msg, $msg_hash) = &check_key_and_xml_validity($input, $module_key, $session_id);
697 if( (not defined $msg) || (not defined $msg_hash) ) {
698 next;
699 }
700 else {
701 $module = $mod;
702 last;
703 }
704 }
706 if( (!$msg) || (!$msg_hash) || (!$module)) {
707 daemon_log("$session_id DEBUG: Incoming message is not from an unknown host", 7);
708 }
710 return ($msg, $msg_hash, $module);
711 }
714 sub create_ciphering {
715 my ($passwd) = @_;
716 if((!defined($passwd)) || length($passwd)==0) {
717 $passwd = "";
718 }
719 $passwd = substr(md5_hex("$passwd") x 32, 0, 32);
720 my $iv = substr(md5_hex('GONICUS GmbH'),0, 16);
721 my $my_cipher = Crypt::Rijndael->new($passwd , Crypt::Rijndael::MODE_CBC());
722 $my_cipher->set_iv($iv);
723 return $my_cipher;
724 }
727 sub encrypt_msg {
728 my ($msg, $key) = @_;
729 my $my_cipher = &create_ciphering($key);
730 my $len;
731 {
732 use bytes;
733 $len= 16-length($msg)%16;
734 }
735 $msg = "\0"x($len).$msg;
736 $msg = $my_cipher->encrypt($msg);
737 chomp($msg = &encode_base64($msg));
738 # there are no newlines allowed inside msg
739 $msg=~ s/\n//g;
740 return $msg;
741 }
744 sub decrypt_msg {
746 my ($msg, $key) = @_ ;
747 $msg = &decode_base64($msg);
748 my $my_cipher = &create_ciphering($key);
749 $msg = $my_cipher->decrypt($msg);
750 $msg =~ s/\0*//g;
751 return $msg;
752 }
755 sub get_encrypt_key {
756 my ($target) = @_ ;
757 my $encrypt_key;
758 my $error = 0;
760 # target can be in known_server
761 if( not defined $encrypt_key ) {
762 my $sql_statement= "SELECT * FROM known_server WHERE hostname='$target'";
763 my $query_res = $known_server_db->select_dbentry( $sql_statement );
764 while( my ($hit_num, $hit) = each %{ $query_res } ) {
765 my $host_name = $hit->{hostname};
766 if( $host_name ne $target ) {
767 next;
768 }
769 $encrypt_key = $hit->{hostkey};
770 last;
771 }
772 }
774 # target can be in known_client
775 if( not defined $encrypt_key ) {
776 my $sql_statement= "SELECT * FROM known_clients WHERE hostname='$target'";
777 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
778 while( my ($hit_num, $hit) = each %{ $query_res } ) {
779 my $host_name = $hit->{hostname};
780 if( $host_name ne $target ) {
781 next;
782 }
783 $encrypt_key = $hit->{hostkey};
784 last;
785 }
786 }
788 return $encrypt_key;
789 }
792 #=== FUNCTION ================================================================
793 # NAME: open_socket
794 # PARAMETERS: PeerAddr string something like 192.168.1.1 or 192.168.1.1:10000
795 # [PeerPort] string necessary if port not appended by PeerAddr
796 # RETURNS: socket IO::Socket::INET
797 # DESCRIPTION: open a socket to PeerAddr
798 #===============================================================================
799 sub open_socket {
800 my ($PeerAddr, $PeerPort) = @_ ;
801 if(defined($PeerPort)){
802 $PeerAddr = $PeerAddr.":".$PeerPort;
803 }
804 my $socket;
805 $socket = new IO::Socket::INET(PeerAddr => $PeerAddr,
806 Porto => "tcp",
807 Type => SOCK_STREAM,
808 Timeout => 5,
809 );
810 if(not defined $socket) {
811 return;
812 }
813 # &daemon_log("DEBUG: open_socket: $PeerAddr", 7);
814 return $socket;
815 }
818 #=== FUNCTION ================================================================
819 # NAME: get_ip
820 # PARAMETERS: interface name (i.e. eth0)
821 # RETURNS: (ip address)
822 # DESCRIPTION: Uses ioctl to get ip address directly from system.
823 #===============================================================================
824 sub get_ip {
825 my $ifreq= shift;
826 my $result= "";
827 my $SIOCGIFADDR= 0x8915; # man 2 ioctl_list
828 my $proto= getprotobyname('ip');
830 socket SOCKET, PF_INET, SOCK_DGRAM, $proto
831 or die "socket: $!";
833 if(ioctl SOCKET, $SIOCGIFADDR, $ifreq) {
834 my ($if, $sin) = unpack 'a16 a16', $ifreq;
835 my ($port, $addr) = sockaddr_in $sin;
836 my $ip = inet_ntoa $addr;
838 if ($ip && length($ip) > 0) {
839 $result = $ip;
840 }
841 }
843 return $result;
844 }
847 sub get_local_ip_for_remote_ip {
848 my $remote_ip= shift;
849 my $result="0.0.0.0";
851 if($remote_ip =~ /^(\d\d?\d?\.){3}\d\d?\d?$/) {
852 if($remote_ip eq "127.0.0.1") {
853 $result = "127.0.0.1";
854 } else {
855 my $PROC_NET_ROUTE= ('/proc/net/route');
857 open(PROC_NET_ROUTE, "<$PROC_NET_ROUTE")
858 or die "Could not open $PROC_NET_ROUTE";
860 my @ifs = <PROC_NET_ROUTE>;
862 close(PROC_NET_ROUTE);
864 # Eat header line
865 shift @ifs;
866 chomp @ifs;
867 foreach my $line(@ifs) {
868 my ($Iface,$Destination,$Gateway,$Flags,$RefCnt,$Use,$Metric,$Mask,$MTU,$Window,$IRTT)=split(/\s/, $line);
869 my $destination;
870 my $mask;
871 my ($d,$c,$b,$a)=unpack('a2 a2 a2 a2', $Destination);
872 $destination= sprintf("%d.%d.%d.%d", hex($a), hex($b), hex($c), hex($d));
873 ($d,$c,$b,$a)=unpack('a2 a2 a2 a2', $Mask);
874 $mask= sprintf("%d.%d.%d.%d", hex($a), hex($b), hex($c), hex($d));
875 if(new NetAddr::IP($remote_ip)->within(new NetAddr::IP($destination, $mask))) {
876 # destination matches route, save mac and exit
877 $result= &get_ip($Iface);
878 last;
879 }
880 }
881 }
882 } else {
883 daemon_log("get_local_ip_for_remote_ip was called with a non-ip parameter: $remote_ip", 1);
884 }
885 return $result;
886 }
889 sub send_msg_to_target {
890 my ($msg, $address, $encrypt_key, $msg_header, $session_id) = @_ ;
891 my $error = 0;
892 my $header;
893 my $new_status;
894 my $act_status;
895 my ($sql_statement, $res);
897 if( $msg_header ) {
898 $header = "'$msg_header'-";
899 } else {
900 $header = "";
901 }
903 # Patch the source ip
904 if($msg =~ /<source>0\.0\.0\.0:\d*?<\/source>/) {
905 my $remote_ip = &get_local_ip_for_remote_ip(sprintf("%s", $address =~ /^([0-9\.]*?):.*$/));
906 $msg =~ s/<source>(0\.0\.0\.0):(\d*?)<\/source>/<source>$remote_ip:$2<\/source>/s;
907 }
909 # encrypt xml msg
910 my $crypted_msg = &encrypt_msg($msg, $encrypt_key);
912 # opensocket
913 my $socket = &open_socket($address);
914 if( !$socket ) {
915 daemon_log("$session_id ERROR: cannot send ".$header."msg to $address , host not reachable", 1);
916 $error++;
917 }
919 if( $error == 0 ) {
920 # send xml msg
921 print $socket $crypted_msg."\n";
923 daemon_log("$session_id INFO: send ".$header."msg to $address", 5);
924 daemon_log("DEBUG: message:\n$msg", 9);
926 }
928 # close socket in any case
929 if( $socket ) {
930 close $socket;
931 }
933 if( $error > 0 ) { $new_status = "down"; }
934 else { $new_status = $msg_header; }
937 # known_clients
938 $sql_statement = "SELECT * FROM known_clients WHERE hostname='$address'";
939 $res = $known_clients_db->select_dbentry($sql_statement);
940 if( keys(%$res) > 0) {
941 $act_status = $res->{1}->{'status'};
942 if ($act_status eq "down" && $new_status eq "down") {
943 $sql_statement = "DELETE FROM known_clients WHERE hostname='$address'";
944 $res = $known_clients_db->del_dbentry($sql_statement);
945 daemon_log("$session_id WARNING: failed 2x to send msg to host '$address', delete host from known_clients", 3);
946 } else {
947 $sql_statement = "UPDATE known_clients SET status='$new_status' WHERE hostname='$address'";
948 $res = $known_clients_db->update_dbentry($sql_statement);
949 if($new_status eq "down"){
950 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
951 } else {
952 daemon_log("$session_id INFO: set '$address' from status '$act_status' to '$new_status'", 5);
953 }
954 }
955 }
957 # known_server
958 $sql_statement = "SELECT * FROM $known_server_tn WHERE hostname='$address'";
959 $res = $known_server_db->select_dbentry($sql_statement);
960 if( keys(%$res) > 0 ) {
961 $act_status = $res->{1}->{'status'};
962 if ($act_status eq "down" && $new_status eq "down") {
963 $sql_statement = "DELETE FROM known_server WHERE hostname='$address'";
964 $res = $known_server_db->del_dbentry($sql_statement);
965 daemon_log("$session_id WARNING: failed 2x to send a message to host '$address', delete host from known_server", 3);
966 }
967 else {
968 $sql_statement = "UPDATE known_server SET status='$new_status' WHERE hostname='$address'";
969 $res = $known_server_db->update_dbentry($sql_statement);
970 if($new_status eq "down"){
971 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
972 }
973 else {
974 daemon_log("$session_id INFO: set '$address' from status '$act_status' to '$new_status'", 5);
975 }
976 }
977 }
978 return $error;
979 }
982 sub update_jobdb_status_for_send_msgs {
983 my ($answer, $error) = @_;
984 if( $answer =~ /<jobdb_id>(\d+)<\/jobdb_id>/ ) {
985 my $jobdb_id = $1;
987 # sending msg faild
988 if( $error ) {
989 if (not $answer =~ /<header>trigger_action_reinstall<\/header>/) {
990 my $sql_statement = "UPDATE $job_queue_tn ".
991 "SET status='error', result='can not deliver msg, please consult log file' ".
992 "WHERE id=$jobdb_id";
993 my $res = $job_db->update_dbentry($sql_statement);
994 }
996 # sending msg was successful
997 } else {
998 my $sql_statement = "UPDATE $job_queue_tn ".
999 "SET status='done' ".
1000 "WHERE id=$jobdb_id AND status='processed'";
1001 my $res = $job_db->update_dbentry($sql_statement);
1002 }
1003 }
1004 }
1006 sub _start {
1007 my ($kernel) = $_[KERNEL];
1008 &trigger_db_loop($kernel);
1009 $global_kernel = $kernel;
1010 $kernel->yield('register_at_foreign_servers');
1011 $kernel->yield('create_fai_server_db', $fai_server_tn );
1012 $kernel->yield('create_fai_release_db', $fai_release_tn );
1013 $kernel->sig(USR1 => "sig_handler");
1014 $kernel->sig(USR2 => "create_packages_list_db");
1015 }
1017 sub sig_handler {
1018 my ($kernel, $signal) = @_[KERNEL, ARG0] ;
1019 daemon_log("0 INFO got signal '$signal'", 1);
1020 $kernel->sig_handled();
1021 return;
1022 }
1025 sub msg_to_decrypt {
1026 my ($session, $heap) = @_[SESSION, HEAP];
1027 my $session_id = $session->ID;
1028 my ($msg, $msg_hash, $module);
1029 my $error = 0;
1031 # hole neue msg aus @msgs_to_decrypt
1032 my $next_msg = shift @msgs_to_decrypt;
1034 # entschlüssle sie
1036 # msg is from a new client or gosa
1037 ($msg, $msg_hash, $module) = &input_from_unknown_host($next_msg, $session_id);
1038 # msg is from a gosa-si-server or gosa-si-bus
1039 if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1040 ($msg, $msg_hash, $module) = &input_from_known_server($next_msg, $heap->{'remote_ip'}, $session_id);
1041 }
1042 # msg is from a gosa-si-client
1043 if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1044 ($msg, $msg_hash, $module) = &input_from_known_client($next_msg, $heap->{'remote_ip'}, $session_id);
1045 }
1046 # an error occurred
1047 if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1048 # if an incoming msg could not be decrypted (maybe a wrong key), send client a ping. If the client
1049 # could not understand a msg from its server the client cause a re-registering process
1050 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);
1051 my $sql_statement = "SELECT * FROM $main::known_clients_tn WHERE (hostname LIKE '".$heap->{'remote_ip'}."%')";
1052 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1053 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1054 my $host_name = $hit->{'hostname'};
1055 my $host_key = $hit->{'hostkey'};
1056 my $ping_msg = "<xml> <header>gosa_ping</header> <source>$server_address</source> <target>$host_name</target></xml>";
1057 my $error = &send_msg_to_target($ping_msg, $host_name, $host_key, "gosa_ping", $session_id);
1058 &update_jobdb_status_for_send_msgs($ping_msg, $error);
1059 }
1060 $error++;
1061 }
1063 # add message to incoming_db
1064 if( $error == 0) {
1065 my $header = @{$msg_hash->{'header'}}[0];
1066 my $target = @{$msg_hash->{'target'}}[0];
1067 my $res = $incoming_db->add_dbentry( {table=>$incoming_tn,
1068 primkey=>[],
1069 headertag=>$header,
1070 targettag=>$target,
1071 xmlmessage=>$msg,
1072 timestamp=>&get_time,
1073 module=>$module,
1074 } );
1075 if ($res != 0) {
1076 # TODO ist das mit $! so ok???
1077 #&daemon_log("$session_id ERROR: cannot add message to incoming.db: $!", 1);
1078 }
1079 }
1081 }
1084 sub next_task {
1085 my ($session, $heap) = @_[SESSION, HEAP];
1086 my $task = POE::Wheel::Run->new(
1087 Program => sub { process_task($session, $heap) },
1088 StdioFilter => POE::Filter::Reference->new(),
1089 StdoutEvent => "task_result",
1090 StderrEvent => "task_debug",
1091 CloseEvent => "task_done",
1092 );
1094 $heap->{task}->{ $task->ID } = $task;
1095 }
1097 sub handle_task_result {
1098 my ($kernel, $heap, $result) = @_[KERNEL, HEAP, ARG0];
1099 my $client_answer = $result->{'answer'};
1100 if( $client_answer =~ s/session_id=(\d+)$// ) {
1101 my $session_id = $1;
1102 if( defined $session_id ) {
1103 my $session_reference = $kernel->ID_id_to_session($session_id);
1104 if( defined $session_reference ) {
1105 $heap = $session_reference->get_heap();
1106 }
1107 }
1109 if(exists $heap->{'client'}) {
1110 $heap->{'client'}->put($client_answer);
1111 }
1112 }
1113 $kernel->sig(CHLD => "child_reap");
1114 }
1116 sub handle_task_debug {
1117 my $result = $_[ARG0];
1118 print STDERR "$result\n";
1119 }
1121 sub handle_task_done {
1122 my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
1123 delete $heap->{task}->{$task_id};
1124 }
1126 sub process_task {
1127 no strict "refs";
1128 my ($session, $heap, $input) = @_;
1129 my $session_id = $session->ID;
1130 my $error = 0;
1131 my $answer_l;
1132 my ($answer_header, @answer_target_l, $answer_source);
1133 my $client_answer = "";
1135 ##################################################
1136 # fetch first unprocessed message from incoming_db
1137 # sometimes the program is faster than sqlite, so wait until informations are present at db
1138 my $id_sql;
1139 my $id_res;
1140 my $message_id;
1141 # TODO : das hier ist sehr sehr unschön
1142 # to be tested: speed enhancement with usleep 100000???
1143 while (1) {
1144 $id_sql = "SELECT min(id) FROM $incoming_tn WHERE (NOT headertag LIKE 'answer%')";
1145 $id_res = $incoming_db->exec_statement($id_sql);
1146 $message_id = @{@$id_res[0]}[0];
1147 if (defined $message_id) { last }
1148 usleep(100000);
1149 }
1151 # fetch new message from incoming_db
1152 my $sql = "SELECT * FROM $incoming_tn WHERE id=$message_id";
1153 my $res = $incoming_db->exec_statement($sql);
1155 # prepare all variables needed to process message
1156 my $msg = @{@$res[0]}[4];
1157 my $incoming_id = @{@$res[0]}[0];
1158 my $module = @{@$res[0]}[5];
1159 my $header = @{@$res[0]}[2];
1160 my $msg_hash = $xml->XMLin($msg, ForceArray=>1);
1162 # messages which are an answer to a still running process should not be processed here
1163 if ($header =~ /^answer_(\d+)/) {
1164 return;
1165 }
1167 # delete message from db
1168 my $delete_sql = "DELETE FROM $incoming_tn WHERE id=$incoming_id";
1169 my $delete_res = $incoming_db->exec_statement($delete_sql);
1171 ######################
1172 # process incoming msg
1173 if( $error == 0) {
1174 daemon_log("$session_id INFO: Incoming msg (session_id=$session_id) with header '".@{$msg_hash->{'header'}}[0].
1175 "' from '".$heap->{'remote_ip'}."'", 5);
1176 daemon_log("$session_id DEBUG: Processing module ".$module, 7);
1177 $answer_l = &{ $module."::process_incoming_msg" }($msg, $msg_hash, $session_id);
1179 if ( 0 < @{$answer_l} ) {
1180 my $answer_str = join("\n", @{$answer_l});
1181 while ($answer_str =~ /<header>(\w+)<\/header>/g) {
1182 daemon_log("$session_id INFO: got answer message with header '$1'", 5);
1183 }
1184 daemon_log("$session_id DEBUG: $module: got answer from module: \n".$answer_str,8);
1185 } else {
1186 daemon_log("$session_id DEBUG: $module: got no answer from module!" ,8);
1187 }
1189 }
1190 if( !$answer_l ) { $error++ };
1192 ########
1193 # answer
1194 if( $error == 0 ) {
1196 foreach my $answer ( @{$answer_l} ) {
1197 # check outgoing msg to xml validity
1198 my $answer_hash = &check_outgoing_xml_validity($answer);
1199 if( not defined $answer_hash ) { next; }
1201 $answer_header = @{$answer_hash->{'header'}}[0];
1202 @answer_target_l = @{$answer_hash->{'target'}};
1203 $answer_source = @{$answer_hash->{'source'}}[0];
1205 # deliver msg to all targets
1206 foreach my $answer_target ( @answer_target_l ) {
1208 # targets of msg are all gosa-si-clients in known_clients_db
1209 if( $answer_target eq "*" ) {
1210 # answer is for all clients
1211 my $sql_statement= "SELECT * FROM known_clients";
1212 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1213 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1214 my $host_name = $hit->{hostname};
1215 my $host_key = $hit->{hostkey};
1216 my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1217 &update_jobdb_status_for_send_msgs($answer, $error);
1218 }
1219 }
1221 # targets of msg are all gosa-si-server in known_server_db
1222 elsif( $answer_target eq "KNOWN_SERVER" ) {
1223 # answer is for all server in known_server
1224 my $sql_statement= "SELECT * FROM known_server";
1225 my $query_res = $known_server_db->select_dbentry( $sql_statement );
1226 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1227 my $host_name = $hit->{hostname};
1228 my $host_key = $hit->{hostkey};
1229 $answer =~ s/KNOWN_SERVER/$host_name/g;
1230 my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1231 &update_jobdb_status_for_send_msgs($answer, $error);
1232 }
1233 }
1235 # target of msg is GOsa
1236 elsif( $answer_target eq "GOSA" ) {
1237 my $session_id = ($1) if $answer =~ /<session_id>(\d+?)<\/session_id>/;
1238 my $add_on = "";
1239 if( defined $session_id ) {
1240 $add_on = ".session_id=$session_id";
1241 }
1242 # answer is for GOSA and has to returned to connected client
1243 my $gosa_answer = &encrypt_msg($answer, $GosaPackages_key);
1244 $client_answer = $gosa_answer.$add_on;
1245 }
1247 # target of msg is job queue at this host
1248 elsif( $answer_target eq "JOBDB") {
1249 $answer =~ /<header>(\S+)<\/header>/;
1250 my $header;
1251 if( defined $1 ) { $header = $1; }
1252 my $error = &send_msg_to_target($answer, $server_address, $GosaPackages_key, $header, $session_id);
1253 &update_jobdb_status_for_send_msgs($answer, $error);
1254 }
1256 # target of msg is a mac address
1257 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 ) {
1258 daemon_log("$session_id INFO: target is mac address '$answer_target', looking for host in known_clients", 5);
1259 my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$answer_target'";
1260 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1261 my $found_ip_flag = 0;
1262 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1263 my $host_name = $hit->{hostname};
1264 my $host_key = $hit->{hostkey};
1265 $answer =~ s/$answer_target/$host_name/g;
1266 daemon_log("$session_id INFO: found host '$host_name', associated to '$answer_target'", 5);
1267 my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1268 &update_jobdb_status_for_send_msgs($answer, $error);
1269 $found_ip_flag++ ;
1270 }
1271 if( $found_ip_flag == 0) {
1272 daemon_log("$session_id WARNING: no host found in known_clients with mac address '$answer_target'", 3);
1273 if( $bus_activ eq "true" ) {
1274 daemon_log("$session_id INFO: try to forward msg '$answer_header' to bus '$bus_address'", 5);
1275 my $sql_statement = "SELECT * FROM known_server WHERE hostname='$bus_address'";
1276 my $query_res = $known_server_db->select_dbentry( $sql_statement );
1277 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1278 my $bus_address = $hit->{hostname};
1279 my $bus_key = $hit->{hostkey};
1280 my $error = &send_msg_to_target($answer, $bus_address, $bus_key, $answer_header, $session_id);
1281 &update_jobdb_status_for_send_msgs($answer, $error);
1282 last;
1283 }
1284 }
1286 }
1288 # answer is for one specific host
1289 } else {
1290 # get encrypt_key
1291 my $encrypt_key = &get_encrypt_key($answer_target);
1292 if( not defined $encrypt_key ) {
1293 # unknown target, forward msg to bus
1294 daemon_log("$session_id WARNING: unknown target '$answer_target'", 3);
1295 if( $bus_activ eq "true" ) {
1296 daemon_log("$session_id INFO: try to forward msg '$answer_header' to bus '$bus_address'", 5);
1297 my $sql_statement = "SELECT * FROM known_server WHERE hostname='$bus_address'";
1298 my $query_res = $known_server_db->select_dbentry( $sql_statement );
1299 my $res_length = keys( %{$query_res} );
1300 if( $res_length == 0 ){
1301 daemon_log("$session_id WARNING: send '$answer_header' to '$bus_address' failed, ".
1302 "no bus found in known_server", 3);
1303 }
1304 else {
1305 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1306 my $bus_key = $hit->{hostkey};
1307 my $error = &send_msg_to_target($answer, $bus_address, $bus_key, $answer_header,$session_id );
1308 &update_jobdb_status_for_send_msgs($answer, $error);
1309 }
1310 }
1311 }
1312 next;
1313 }
1314 my $error = &send_msg_to_target($answer, $answer_target, $encrypt_key, $answer_header,$session_id);
1315 &update_jobdb_status_for_send_msgs($answer, $error);
1316 }
1317 }
1318 }
1319 }
1321 my $filter = POE::Filter::Reference->new();
1322 my %result = (
1323 status => "seems ok to me",
1324 answer => $client_answer,
1325 );
1327 my $output = $filter->put( [ \%result ] );
1328 print @$output;
1331 }
1334 sub trigger_db_loop {
1335 my ($kernel) = @_ ;
1336 $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1337 $kernel->delay_set('watch_for_done_jobs', $job_queue_loop_delay);
1338 $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay);
1339 $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay);
1340 $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay);
1341 }
1344 sub watch_for_done_jobs {
1345 my ($kernel,$heap) = @_[KERNEL, HEAP];
1347 my $sql_statement = "SELECT * FROM ".$job_queue_tn.
1348 " WHERE status='done'";
1349 my $res = $job_db->select_dbentry( $sql_statement );
1351 while( my ($id, $hit) = each %{$res} ) {
1352 my $jobdb_id = $hit->{id};
1353 my $sql_statement = "DELETE FROM $job_queue_tn WHERE id=$jobdb_id";
1354 my $res = $job_db->del_dbentry($sql_statement);
1355 }
1357 $kernel->delay_set('watch_for_done_jobs',$job_queue_loop_delay);
1358 }
1361 sub watch_for_new_jobs {
1362 if($watch_for_new_jobs_in_progress == 0) {
1363 $watch_for_new_jobs_in_progress = 1;
1364 my ($kernel,$heap) = @_[KERNEL, HEAP];
1366 # check gosa job queue for jobs with executable timestamp
1367 my $timestamp = &get_time();
1368 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE status='waiting' AND (CAST (timestamp AS INTEGER)) < $timestamp ORDER BY timestamp";
1369 my $res = $job_db->exec_statement( $sql_statement );
1371 # Merge all new jobs that would do the same actions
1372 my @drops;
1373 my $hits;
1374 foreach my $hit (reverse @{$res} ) {
1375 my $macaddress= lc @{$hit}[8];
1376 my $headertag= @{$hit}[5];
1377 if(
1378 defined($hits->{$macaddress}) &&
1379 defined($hits->{$macaddress}->{$headertag}) &&
1380 defined($hits->{$macaddress}->{$headertag}[0])
1381 ) {
1382 push @drops, "DELETE FROM $job_queue_tn WHERE id = $hits->{$macaddress}->{$headertag}[0]";
1383 }
1384 $hits->{$macaddress}->{$headertag}= $hit;
1385 }
1387 # Delete new jobs with a matching job in state 'processing'
1388 foreach my $macaddress (keys %{$hits}) {
1389 foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1390 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1391 if(defined($jobdb_id)) {
1392 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND headertag='$jobdb_headertag' AND status='processing'";
1393 my $res = $job_db->exec_statement( $sql_statement );
1394 foreach my $hit (@{$res}) {
1395 push @drops, "DELETE FROM $job_queue_tn WHERE id=$jobdb_id";
1396 }
1397 } else {
1398 daemon_log("J ERROR: Job without id exists for macaddress $macaddress!", 1);
1399 }
1400 }
1401 }
1403 # Commit deletion
1404 $job_db->exec_statementlist(\@drops);
1406 # Look for new jobs that could be executed
1407 foreach my $macaddress (keys %{$hits}) {
1409 # Look if there is an executing job
1410 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND status='processing'";
1411 my $res = $job_db->exec_statement( $sql_statement );
1413 # Skip new jobs for host if there is a processing job
1414 if(defined($res) and defined @{$res}[0]) {
1415 next;
1416 }
1418 foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1419 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1420 if(defined($jobdb_id)) {
1421 my $job_msg = @{$hits->{$macaddress}->{$jobdb_headertag}}[7];
1423 daemon_log("J DEBUG: its time to execute $job_msg", 7);
1424 my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$macaddress'";
1425 my $res_hash = $known_clients_db->select_dbentry( $sql_statement );
1427 # expect macaddress is unique!!!!!!
1428 my $target = $res_hash->{1}->{hostname};
1430 # change header
1431 $job_msg =~ s/<header>job_/<header>gosa_/;
1433 # add sqlite_id
1434 $job_msg =~ s/<\/xml>$/<jobdb_id>$jobdb_id<\/jobdb_id><\/xml>/;
1436 $job_msg =~ /<header>(\S+)<\/header>/;
1437 my $header = $1 ;
1438 my $func_error = &send_msg_to_target($job_msg, $server_address, $GosaPackages_key, $header, "J");
1440 # update status in job queue to 'processing'
1441 $sql_statement = "UPDATE $job_queue_tn SET status='processing' WHERE id=$jobdb_id";
1442 my $res = $job_db->update_dbentry($sql_statement);
1443 # TODO: abfangen ob alles in ordnung ist oder nicht, wenn nicht error schmeißen
1445 # We don't want parallel processing
1446 last;
1447 }
1448 }
1449 }
1451 $watch_for_new_jobs_in_progress = 0;
1452 $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1453 }
1454 }
1457 sub watch_for_new_messages {
1458 my ($kernel,$heap) = @_[KERNEL, HEAP];
1459 my @coll_user_msg; # collection list of outgoing messages
1461 # check messaging_db for new incoming messages with executable timestamp
1462 my $timestamp = &get_time();
1463 my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( (CAST(timestamp AS INTEGER))<$timestamp AND flag='n' AND direction='in' )";
1464 my $res = $messaging_db->exec_statement( $sql_statement );
1465 foreach my $hit (@{$res}) {
1467 # create outgoing messages
1468 my $message_to = @{$hit}[3];
1469 # translate message_to to plain login name
1470 my @message_to_l = split(/,/, $message_to);
1471 my %receiver_h;
1472 foreach my $receiver (@message_to_l) {
1473 if ($receiver =~ /^u_([\s\S]*)$/) {
1474 $receiver_h{$1} = 0;
1475 } elsif ($receiver =~ /^g_([\s\S]*)$/) {
1476 my $group_name = $1;
1477 # fetch all group members from ldap and add them to receiver hash
1478 my $ldap_handle = &get_ldap_handle();
1479 if (defined $ldap_handle) {
1480 my $mesg = $ldap_handle->search(
1481 base => $ldap_base,
1482 scope => 'sub',
1483 attrs => ['memberUid'],
1484 filter => "cn=$group_name",
1485 );
1486 if ($mesg->count) {
1487 my @entries = $mesg->entries;
1488 foreach my $entry (@entries) {
1489 my @receivers= $entry->get_value("memberUid");
1490 foreach my $receiver (@receivers) {
1491 $receiver_h{$1} = 0;
1492 }
1493 }
1494 }
1495 # translating errors ?
1496 if ($mesg->code) {
1497 daemon_log("M ERROR: unable to translate group '$group_name' to user list for message delivery: $mesg->error", 1);
1498 }
1499 # ldap handle error ?
1500 } else {
1501 daemon_log("M ERROR: unable to translate group '$group_name' to user list for message delivery: no ldap handle available", 1);
1502 }
1503 } else {
1504 my $sbjct = &encode_base64(@{$hit}[1]);
1505 my $msg = &encode_base64(@{$hit}[7]);
1506 &daemon_log("M WARNING: unknown receiver '$receiver' for a user-message '$sbjct - $msg'", 3);
1507 }
1508 }
1509 my @receiver_l = keys(%receiver_h);
1511 my $message_id = @{$hit}[0];
1513 #add each outgoing msg to messaging_db
1514 my $receiver;
1515 foreach $receiver (@receiver_l) {
1516 my $sql_statement = "INSERT INTO $messaging_tn (id, subject, message_from, message_to, flag, direction, delivery_time, message, timestamp) ".
1517 "VALUES ('".
1518 $message_id."', '". # id
1519 @{$hit}[1]."', '". # subject
1520 @{$hit}[2]."', '". # message_from
1521 $receiver."', '". # message_to
1522 "none"."', '". # flag
1523 "out"."', '". # direction
1524 @{$hit}[6]."', '". # delivery_time
1525 @{$hit}[7]."', '". # message
1526 $timestamp."'". # timestamp
1527 ")";
1528 &daemon_log("M DEBUG: $sql_statement", 1);
1529 my $res = $messaging_db->exec_statement($sql_statement);
1530 &daemon_log("M INFO: message '".@{$hit}[0]."' is prepared for delivery to receiver '$receiver'", 5);
1531 }
1533 # set incoming message to flag d=deliverd
1534 $sql_statement = "UPDATE $messaging_tn SET flag='p' WHERE id='$message_id'";
1535 &daemon_log("M DEBUG: $sql_statement", 7);
1536 $res = $messaging_db->update_dbentry($sql_statement);
1537 &daemon_log("M INFO: message '$message_id' is set to flag 'p' (processed)", 5);
1538 }
1540 $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay);
1541 return;
1542 }
1544 sub watch_for_delivery_messages {
1545 my ($kernel, $heap) = @_[KERNEL, HEAP];
1547 # select outgoing messages
1548 my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( flag='p' AND direction='out' )";
1549 #&daemon_log("0 DEBUG: $sql", 7);
1550 my $res = $messaging_db->exec_statement( $sql_statement );
1552 # build out msg for each usr
1553 foreach my $hit (@{$res}) {
1554 my $receiver = @{$hit}[3];
1555 my $msg_id = @{$hit}[0];
1556 my $subject = @{$hit}[1];
1557 my $message = @{$hit}[7];
1559 # resolve usr -> host where usr is logged in
1560 my $sql = "SELECT * FROM $login_users_tn WHERE (user='$receiver')";
1561 #&daemon_log("0 DEBUG: $sql", 7);
1562 my $res = $login_users_db->exec_statement($sql);
1564 # reciver is logged in nowhere
1565 if (not ref(@$res[0]) eq "ARRAY") { next; }
1567 my $send_succeed = 0;
1568 foreach my $hit (@$res) {
1569 my $receiver_host = @$hit[0];
1570 &daemon_log("M DEBUG: user '$receiver' is logged in at host '$receiver_host'", 7);
1572 # fetch key to encrypt msg propperly for usr/host
1573 my $sql = "SELECT * FROM $known_clients_tn WHERE (hostname='$receiver_host')";
1574 &daemon_log("0 DEBUG: $sql", 7);
1575 my $res = $known_clients_db->exec_statement($sql);
1577 # host is already down
1578 if (not ref(@$res[0]) eq "ARRAY") { next; }
1580 # host is on
1581 my $receiver_key = @{@{$res}[0]}[2];
1582 my %data = ('subject' => $subject, 'message' => $message, 'usr' => $receiver);
1583 my $out_msg = &build_msg("usr_msg", $server_address, $receiver_host, \%data );
1584 my $error = &send_msg_to_target($out_msg, $receiver_host, $receiver_key, "usr_msg", 0);
1585 if ($error == 0 ) {
1586 $send_succeed++ ;
1587 }
1588 }
1590 if ($send_succeed) {
1591 # set outgoing msg at db to deliverd
1592 my $sql = "UPDATE $messaging_tn SET flag='d' WHERE (id='$msg_id' AND direction='out' AND message_to='$receiver')";
1593 &daemon_log("0 DEBUG: $sql", 7);
1594 my $res = $messaging_db->exec_statement($sql);
1595 }
1596 }
1598 $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay);
1599 return;
1600 }
1603 sub watch_for_done_messages {
1604 my ($kernel,$heap) = @_[KERNEL, HEAP];
1606 my $sql = "SELECT * FROM $messaging_tn WHERE (flag='p' AND direction='in')";
1607 #&daemon_log("0 DEBUG: $sql", 7);
1608 my $res = $messaging_db->exec_statement($sql);
1610 foreach my $hit (@{$res}) {
1611 my $msg_id = @{$hit}[0];
1613 my $sql = "SELECT * FROM $messaging_tn WHERE (id='$msg_id' AND direction='out' AND (NOT flag='s'))";
1614 #&daemon_log("0 DEBUG: $sql", 7);
1615 my $res = $messaging_db->exec_statement($sql);
1617 # not all usr msgs have been seen till now
1618 if ( ref(@$res[0]) eq "ARRAY") { next; }
1620 $sql = "DELETE FROM $messaging_tn WHERE (id='$msg_id')";
1621 #&daemon_log("0 DEBUG: $sql", 7);
1622 $res = $messaging_db->exec_statement($sql);
1624 }
1626 $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay);
1627 return;
1628 }
1631 sub get_ldap_handle {
1632 my ($session_id) = @_;
1633 my $heap;
1634 my $ldap_handle;
1636 if (not defined $session_id ) { $session_id = 0 };
1637 if ($session_id =~ /[^0-9]*/) { $session_id = 0 };
1639 if ($session_id == 0) {
1640 daemon_log("$session_id DEBUG: get_ldap_handle invoked without a session_id, create a new ldap_handle", 7);
1641 $ldap_handle = Net::LDAP->new( $ldap_uri );
1642 $ldap_handle->bind($ldap_admin_dn, password => $ldap_admin_password);
1644 } else {
1645 my $session_reference = $global_kernel->ID_id_to_session($session_id);
1646 if( defined $session_reference ) {
1647 $heap = $session_reference->get_heap();
1648 }
1650 if (not defined $heap) {
1651 daemon_log("$session_id DEBUG: cannot get heap for session_id '$session_id'", 7);
1652 return;
1653 }
1655 # TODO: This "if" is nonsense, because it doesn't prove that the
1656 # used handle is still valid - or if we've to reconnect...
1657 #if (not exists $heap->{ldap_handle}) {
1658 $ldap_handle = Net::LDAP->new( $ldap_uri );
1659 $ldap_handle->bind($ldap_admin_dn, password => $ldap_admin_password);
1660 $heap->{ldap_handle} = $ldap_handle;
1661 #}
1662 }
1663 return $ldap_handle;
1664 }
1667 sub change_fai_state {
1668 my ($st, $targets, $session_id) = @_;
1669 $session_id = 0 if not defined $session_id;
1670 # Set FAI state to localboot
1671 my %mapActions= (
1672 reboot => '',
1673 update => 'softupdate',
1674 localboot => 'localboot',
1675 reinstall => 'install',
1676 rescan => '',
1677 wake => '',
1678 memcheck => 'memcheck',
1679 sysinfo => 'sysinfo',
1680 install => 'install',
1681 );
1683 # Return if this is unknown
1684 if (!exists $mapActions{ $st }){
1685 daemon_log("$session_id ERROR: unknown action '$st', can not translate ot FAIstate", 1);
1686 return;
1687 }
1689 my $state= $mapActions{ $st };
1691 my $ldap_handle = &get_ldap_handle($session_id);
1692 if( defined($ldap_handle) ) {
1694 # Build search filter for hosts
1695 my $search= "(&(objectClass=GOhard)";
1696 foreach (@{$targets}){
1697 $search.= "(macAddress=$_)";
1698 }
1699 $search.= ")";
1701 # If there's any host inside of the search string, procress them
1702 if (!($search =~ /macAddress/)){
1703 daemon_log("$session_id ERROR: no macAddress found in filter statement for LDAP search: '$search'", 1);
1704 return;
1705 }
1707 # Perform search for Unit Tag
1708 my $mesg = $ldap_handle->search(
1709 base => $ldap_base,
1710 scope => 'sub',
1711 attrs => ['dn', 'FAIstate', 'objectClass'],
1712 filter => "$search"
1713 );
1715 if ($mesg->count) {
1716 my @entries = $mesg->entries;
1717 foreach my $entry (@entries) {
1718 # Only modify entry if it is not set to '$state'
1719 if ($entry->get_value("FAIstate") ne "$state"){
1720 daemon_log("$session_id INFO: Setting FAIstate to '$state' for ".$entry->dn, 5);
1721 my $result;
1722 my %tmp = map { $_ => 1 } $entry->get_value("objectClass");
1723 if (exists $tmp{'FAIobject'}){
1724 if ($state eq ''){
1725 $result= $ldap_handle->modify($entry->dn, changes => [
1726 delete => [ FAIstate => [] ] ]);
1727 } else {
1728 $result= $ldap_handle->modify($entry->dn, changes => [
1729 replace => [ FAIstate => $state ] ]);
1730 }
1731 } elsif ($state ne ''){
1732 $result= $ldap_handle->modify($entry->dn, changes => [
1733 add => [ objectClass => 'FAIobject' ],
1734 add => [ FAIstate => $state ] ]);
1735 }
1737 # Errors?
1738 if ($result->code){
1739 daemon_log("$session_id Error: Setting FAIstate to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
1740 }
1741 } else {
1742 daemon_log("$session_id DEBUG FAIstate at host '".$entry->dn."' already at state '$st'", 7);
1743 }
1744 }
1745 }
1746 # if no ldap handle defined
1747 } else {
1748 daemon_log("$session_id ERROR: no LDAP handle defined for update FAIstate", 1);
1749 }
1751 }
1754 sub change_goto_state {
1755 my ($st, $targets, $session_id) = @_;
1756 $session_id = 0 if not defined $session_id;
1758 # Switch on or off?
1759 my $state= $st eq 'active' ? 'active': 'locked';
1761 my $ldap_handle = &get_ldap_handle($session_id);
1762 if( defined($ldap_handle) ) {
1764 # Build search filter for hosts
1765 my $search= "(&(objectClass=GOhard)";
1766 foreach (@{$targets}){
1767 $search.= "(macAddress=$_)";
1768 }
1769 $search.= ")";
1771 # If there's any host inside of the search string, procress them
1772 if (!($search =~ /macAddress/)){
1773 return;
1774 }
1776 # Perform search for Unit Tag
1777 my $mesg = $ldap_handle->search(
1778 base => $ldap_base,
1779 scope => 'sub',
1780 attrs => ['dn', 'gotoMode'],
1781 filter => "$search"
1782 );
1784 if ($mesg->count) {
1785 my @entries = $mesg->entries;
1786 foreach my $entry (@entries) {
1788 # Only modify entry if it is not set to '$state'
1789 if ($entry->get_value("gotoMode") ne $state){
1791 daemon_log("$session_id INFO: Setting gotoMode to '$state' for ".$entry->dn, 5);
1792 my $result;
1793 $result= $ldap_handle->modify($entry->dn, changes => [
1794 replace => [ gotoMode => $state ] ]);
1796 # Errors?
1797 if ($result->code){
1798 &daemon_log("$session_id Error: Setting gotoMode to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
1799 }
1801 }
1802 }
1803 }
1805 }
1806 }
1809 sub run_create_fai_server_db {
1810 my ($kernel, $session, $heap, $table_name) = @_[KERNEL, SESSION, HEAP, ARG0];
1811 my $session_id = $session->ID;
1812 my $task = POE::Wheel::Run->new(
1813 Program => sub { &create_fai_server_db($table_name,$kernel, undef, $session_id) },
1814 StdoutEvent => "session_run_result",
1815 StderrEvent => "session_run_debug",
1816 CloseEvent => "session_run_done",
1817 );
1819 $heap->{task}->{ $task->ID } = $task;
1820 return;
1821 }
1824 sub create_fai_server_db {
1825 my ($table_name, $kernel, $dont_create_packages_list, $session_id) = @_;
1826 my $result;
1828 if (not defined $session_id) { $session_id = 0; }
1829 my $ldap_handle = &get_ldap_handle();
1830 if(defined($ldap_handle)) {
1831 daemon_log("$session_id INFO: create_fai_server_db: start", 5);
1832 my $mesg= $ldap_handle->search(
1833 base => $ldap_base,
1834 scope => 'sub',
1835 attrs => ['FAIrepository', 'gosaUnitTag'],
1836 filter => "(&(FAIrepository=*)(objectClass=FAIrepositoryServer))",
1837 );
1838 if($mesg->{'resultCode'} == 0 &&
1839 $mesg->count != 0) {
1840 foreach my $entry (@{$mesg->{entries}}) {
1841 if($entry->exists('FAIrepository')) {
1842 # Add an entry for each Repository configured for server
1843 foreach my $repo(@{$entry->get_value('FAIrepository', asref => 1)}) {
1844 my($tmp_url,$tmp_server,$tmp_release,$tmp_sections) = split(/\|/, $repo);
1845 my $tmp_tag= $entry->get_value('gosaUnitTag') || "";
1846 $result= $fai_server_db->add_dbentry( {
1847 table => $table_name,
1848 primkey => ['server', 'release', 'tag'],
1849 server => $tmp_url,
1850 release => $tmp_release,
1851 sections => $tmp_sections,
1852 tag => (length($tmp_tag)>0)?$tmp_tag:"",
1853 } );
1854 }
1855 }
1856 }
1857 }
1858 daemon_log("$session_id INFO: create_fai_server_db: finished", 5);
1860 # TODO: Find a way to post the 'create_packages_list_db' event
1861 if(not defined($dont_create_packages_list)) {
1862 &create_packages_list_db(undef, undef, $session_id);
1863 }
1864 }
1866 $ldap_handle->disconnect;
1867 return $result;
1868 }
1871 sub run_create_fai_release_db {
1872 my ($session, $heap, $table_name) = @_[SESSION, HEAP, ARG0];
1873 my $session_id = $session->ID;
1874 my $task = POE::Wheel::Run->new(
1875 Program => sub { &create_fai_release_db($table_name, $session_id) },
1876 StdoutEvent => "session_run_result",
1877 StderrEvent => "session_run_debug",
1878 CloseEvent => "session_run_done",
1879 );
1881 $heap->{task}->{ $task->ID } = $task;
1882 return;
1883 }
1886 sub create_fai_release_db {
1887 my ($table_name, $session_id) = @_;
1888 my $result;
1890 # used for logging
1891 if (not defined $session_id) { $session_id = 0; }
1893 my $ldap_handle = &get_ldap_handle();
1894 if(defined($ldap_handle)) {
1895 daemon_log("$session_id INFO: create_fai_release_db: start",5);
1896 my $mesg= $ldap_handle->search(
1897 base => $ldap_base,
1898 scope => 'sub',
1899 attrs => [],
1900 filter => "(&(objectClass=organizationalUnit)(ou=fai))",
1901 );
1902 if($mesg->{'resultCode'} == 0 &&
1903 $mesg->count != 0) {
1904 # Walk through all possible FAI container ou's
1905 my @sql_list;
1906 my $timestamp= &get_time();
1907 foreach my $ou (@{$mesg->{entries}}) {
1908 my $tmp_classes= resolve_fai_classes($ou->dn, $ldap_handle, $session_id);
1909 if(defined($tmp_classes) && ref($tmp_classes) eq 'HASH') {
1910 my @tmp_array=get_fai_release_entries($tmp_classes);
1911 if(@tmp_array) {
1912 foreach my $entry (@tmp_array) {
1913 if(defined($entry) && ref($entry) eq 'HASH') {
1914 my $sql=
1915 "INSERT INTO $table_name "
1916 ."(timestamp, release, class, type, state) VALUES ("
1917 .$timestamp.","
1918 ."'".$entry->{'release'}."',"
1919 ."'".$entry->{'class'}."',"
1920 ."'".$entry->{'type'}."',"
1921 ."'".$entry->{'state'}."')";
1922 push @sql_list, $sql;
1923 }
1924 }
1925 }
1926 }
1927 }
1929 daemon_log("$session_id DEBUG: Inserting ".scalar @sql_list." entries to DB",8);
1930 if(@sql_list) {
1931 unshift @sql_list, "VACUUM";
1932 unshift @sql_list, "DELETE FROM $table_name";
1933 $fai_release_db->exec_statementlist(\@sql_list);
1934 }
1935 daemon_log("$session_id DEBUG: Done with inserting",7);
1936 }
1937 daemon_log("$session_id INFO: create_fai_release_db: finished",5);
1938 }
1939 $ldap_handle->disconnect;
1940 return $result;
1941 }
1943 sub get_fai_types {
1944 my $tmp_classes = shift || return undef;
1945 my @result;
1947 foreach my $type(keys %{$tmp_classes}) {
1948 if(defined($tmp_classes->{$type}[0]) && (!($tmp_classes->{$type}[0] =~ /^.*?removed.*?$/))) {
1949 my $entry = {
1950 type => $type,
1951 state => $tmp_classes->{$type}[0],
1952 };
1953 push @result, $entry;
1954 }
1955 }
1957 return @result;
1958 }
1960 sub get_fai_state {
1961 my $result = "";
1962 my $tmp_classes = shift || return $result;
1964 foreach my $type(keys %{$tmp_classes}) {
1965 if(defined($tmp_classes->{$type}[0])) {
1966 $result = $tmp_classes->{$type}[0];
1968 # State is equal for all types in class
1969 last;
1970 }
1971 }
1973 return $result;
1974 }
1976 sub resolve_fai_classes {
1977 my ($fai_base, $ldap_handle, $session_id) = @_;
1978 if (not defined $session_id) { $session_id = 0; }
1979 my $result;
1980 my @possible_fai_classes= ("FAIscript", "FAIhook", "FAIpartitionTable", "FAItemplate", "FAIvariable", "FAIprofile", "FAIpackageList");
1981 my $fai_filter= "(|(&(objectClass=FAIclass)(|(objectClass=".join(")(objectClass=", @possible_fai_classes).")))(objectClass=FAIbranch))";
1982 my $fai_classes;
1984 daemon_log("$session_id DEBUG: Searching for FAI entries in base $fai_base",7);
1985 my $mesg= $ldap_handle->search(
1986 base => $fai_base,
1987 scope => 'sub',
1988 attrs => ['cn','objectClass','FAIstate'],
1989 filter => $fai_filter,
1990 );
1991 daemon_log("$session_id DEBUG: Found ".$mesg->count()." FAI entries",7);
1993 if($mesg->{'resultCode'} == 0 &&
1994 $mesg->count != 0) {
1995 foreach my $entry (@{$mesg->{entries}}) {
1996 if($entry->exists('cn')) {
1997 my $tmp_dn= $entry->dn();
1999 # Skip classname and ou dn parts for class
2000 my $tmp_release = ($1) if $tmp_dn =~ /^[^,]+,[^,]+,(.*?),$fai_base$/;
2002 # Skip classes without releases
2003 if((!defined($tmp_release)) || length($tmp_release)==0) {
2004 next;
2005 }
2007 my $tmp_cn= $entry->get_value('cn');
2008 my $tmp_state= $entry->get_value('FAIstate');
2010 my $tmp_type;
2011 # Get FAI type
2012 for my $oclass(@{$entry->get_value('objectClass', asref => 1)}) {
2013 if(grep $_ eq $oclass, @possible_fai_classes) {
2014 $tmp_type= $oclass;
2015 last;
2016 }
2017 }
2019 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
2020 # A Subrelease
2021 my @sub_releases = split(/,/, $tmp_release);
2023 # Walk through subreleases and build hash tree
2024 my $hash;
2025 while(my $tmp_sub_release = pop @sub_releases) {
2026 $hash .= "\{'$tmp_sub_release'\}->";
2027 }
2028 eval('push @{$fai_classes->'.$hash.'{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";');
2029 } else {
2030 # A branch, no subrelease
2031 push @{$fai_classes->{$tmp_release}->{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";
2032 }
2033 } elsif (!$entry->exists('cn')) {
2034 my $tmp_dn= $entry->dn();
2035 my $tmp_release = ($1) if $tmp_dn =~ /^(.*?),$fai_base$/;
2037 # Skip classes without releases
2038 if((!defined($tmp_release)) || length($tmp_release)==0) {
2039 next;
2040 }
2042 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
2043 # A Subrelease
2044 my @sub_releases= split(/,/, $tmp_release);
2046 # Walk through subreleases and build hash tree
2047 my $hash;
2048 while(my $tmp_sub_release = pop @sub_releases) {
2049 $hash .= "\{'$tmp_sub_release'\}->";
2050 }
2051 # Remove the last two characters
2052 chop($hash);
2053 chop($hash);
2055 eval('$fai_classes->'.$hash.'= {}');
2056 } else {
2057 # A branch, no subrelease
2058 if(!exists($fai_classes->{$tmp_release})) {
2059 $fai_classes->{$tmp_release} = {};
2060 }
2061 }
2062 }
2063 }
2065 # The hash is complete, now we can honor the copy-on-write based missing entries
2066 foreach my $release (keys %$fai_classes) {
2067 $result->{$release}= deep_copy(apply_fai_inheritance($fai_classes->{$release}));
2068 }
2069 }
2070 return $result;
2071 }
2073 sub apply_fai_inheritance {
2074 my $fai_classes = shift || return {};
2075 my $tmp_classes;
2077 # Get the classes from the branch
2078 foreach my $class (keys %{$fai_classes}) {
2079 # Skip subreleases
2080 if($class =~ /^ou=.*$/) {
2081 next;
2082 } else {
2083 $tmp_classes->{$class}= deep_copy($fai_classes->{$class});
2084 }
2085 }
2087 # Apply to each subrelease
2088 foreach my $subrelease (keys %{$fai_classes}) {
2089 if($subrelease =~ /ou=/) {
2090 foreach my $tmp_class (keys %{$tmp_classes}) {
2091 if(!exists($fai_classes->{$subrelease}->{$tmp_class})) {
2092 $fai_classes->{$subrelease}->{$tmp_class} =
2093 deep_copy($tmp_classes->{$tmp_class});
2094 } else {
2095 foreach my $type (keys %{$tmp_classes->{$tmp_class}}) {
2096 if(!exists($fai_classes->{$subrelease}->{$tmp_class}->{$type})) {
2097 $fai_classes->{$subrelease}->{$tmp_class}->{$type}=
2098 deep_copy($tmp_classes->{$tmp_class}->{$type});
2099 }
2100 }
2101 }
2102 }
2103 }
2104 }
2106 # Find subreleases in deeper levels
2107 foreach my $subrelease (keys %{$fai_classes}) {
2108 if($subrelease =~ /ou=/) {
2109 foreach my $subsubrelease (keys %{$fai_classes->{$subrelease}}) {
2110 if($subsubrelease =~ /ou=/) {
2111 apply_fai_inheritance($fai_classes->{$subrelease});
2112 }
2113 }
2114 }
2115 }
2117 return $fai_classes;
2118 }
2120 sub get_fai_release_entries {
2121 my $tmp_classes = shift || return;
2122 my $parent = shift || "";
2123 my @result = shift || ();
2125 foreach my $entry (keys %{$tmp_classes}) {
2126 if(defined($entry)) {
2127 if($entry =~ /^ou=.*$/) {
2128 my $release_name = $entry;
2129 $release_name =~ s/ou=//g;
2130 if(length($parent)>0) {
2131 $release_name = $parent."/".$release_name;
2132 }
2133 my @bufentries = get_fai_release_entries($tmp_classes->{$entry}, $release_name, @result);
2134 foreach my $bufentry(@bufentries) {
2135 push @result, $bufentry;
2136 }
2137 } else {
2138 my @types = get_fai_types($tmp_classes->{$entry});
2139 foreach my $type (@types) {
2140 push @result,
2141 {
2142 'class' => $entry,
2143 'type' => $type->{'type'},
2144 'release' => $parent,
2145 'state' => $type->{'state'},
2146 };
2147 }
2148 }
2149 }
2150 }
2152 return @result;
2153 }
2155 sub deep_copy {
2156 my $this = shift;
2157 if (not ref $this) {
2158 $this;
2159 } elsif (ref $this eq "ARRAY") {
2160 [map deep_copy($_), @$this];
2161 } elsif (ref $this eq "HASH") {
2162 +{map { $_ => deep_copy($this->{$_}) } keys %$this};
2163 } else { die "what type is $_?" }
2164 }
2167 sub session_run_result {
2168 my ($kernel, $heap, $client_answer) = @_[KERNEL, HEAP, ARG0];
2169 $kernel->sig(CHLD => "child_reap");
2170 }
2172 sub session_run_debug {
2173 my $result = $_[ARG0];
2174 print STDERR "$result\n";
2175 }
2177 sub session_run_done {
2178 my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
2179 delete $heap->{task}->{$task_id};
2180 }
2183 sub create_sources_list {
2184 my $session_id = shift;
2185 my $ldap_handle = &main::get_ldap_handle;
2186 my $result="/tmp/gosa_si_tmp_sources_list";
2188 # Remove old file
2189 if(stat($result)) {
2190 unlink($result);
2191 &main::daemon_log("$session_id DEBUG: remove an old version of '$result'", 7);
2192 }
2194 my $fh;
2195 open($fh, ">$result");
2196 if (not defined $fh) {
2197 &main::daemon_log("$session_id DEBUG: cannot open '$result' for writing", 7);
2198 return undef;
2199 }
2200 if(defined($main::ldap_server_dn) and length($main::ldap_server_dn) > 0) {
2201 my $mesg=$ldap_handle->search(
2202 base => $main::ldap_server_dn,
2203 scope => 'base',
2204 attrs => 'FAIrepository',
2205 filter => 'objectClass=FAIrepositoryServer'
2206 );
2207 if($mesg->count) {
2208 foreach my $entry(@{$mesg->{'entries'}}) {
2209 foreach my $value(@{$entry->get_value('FAIrepository', asref => 1)}) {
2210 my ($server, $tag, $release, $sections)= split /\|/, $value;
2211 my $line = "deb $server $release";
2212 $sections =~ s/,/ /g;
2213 $line.= " $sections";
2214 print $fh $line."\n";
2215 }
2216 }
2217 }
2218 } else {
2219 if (defined $main::ldap_server_dn){
2220 &main::daemon_log("$session_id ERROR: something wrong with ldap_server_dn '$main::ldap_server_dn', abort create_sources_list", 1);
2221 } else {
2222 &main::daemon_log("$session_id ERROR: no ldap_server_dn found, abort create_sources_list", 1);
2223 }
2224 }
2225 close($fh);
2227 return $result;
2228 }
2231 sub run_create_packages_list_db {
2232 my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
2233 my $session_id = $session->ID;
2235 my $task = POE::Wheel::Run->new(
2236 Priority => +20,
2237 Program => sub {&create_packages_list_db(undef, undef, $session_id)},
2238 StdoutEvent => "session_run_result",
2239 StderrEvent => "session_run_debug",
2240 CloseEvent => "session_run_done",
2241 );
2242 $heap->{task}->{ $task->ID } = $task;
2243 }
2246 sub create_packages_list_db {
2247 my ($ldap_handle, $sources_file, $session_id) = @_;
2249 # it should not be possible to trigger a recreation of packages_list_db
2250 # while packages_list_db is under construction, so set flag packages_list_under_construction
2251 # which is tested befor recreation can be started
2252 if (-r $packages_list_under_construction) {
2253 daemon_log("$session_id WARNING: packages_list_db is right now under construction, please wait until this process is finished", 3);
2254 return;
2255 } else {
2256 daemon_log("$session_id INFO: create_packages_list_db: start", 5);
2257 # set packages_list_under_construction to true
2258 system("touch $packages_list_under_construction");
2259 @packages_list_statements=();
2260 }
2262 if (not defined $session_id) { $session_id = 0; }
2263 if (not defined $ldap_handle) {
2264 $ldap_handle= &get_ldap_handle();
2266 if (not defined $ldap_handle) {
2267 daemon_log("$session_id ERROR: no ldap_handle available to create_packages_list_db", 1);
2268 unlink($packages_list_under_construction);
2269 return;
2270 }
2271 }
2272 if (not defined $sources_file) {
2273 &main::daemon_log("$session_id INFO: no sources_file given for creating packages list so trigger creation of it", 5);
2274 $sources_file = &create_sources_list($session_id);
2275 }
2277 if (not defined $sources_file) {
2278 &main::daemon_log("$session_id ERROR: no sources_file given under '$sources_file', skip create_packages_list_db", 1);
2279 unlink($packages_list_under_construction);
2280 return;
2281 }
2283 my $line;
2285 open(CONFIG, "<$sources_file") or do {
2286 daemon_log( "$session_id ERROR: create_packages_list_db: Failed to open '$sources_file'", 1);
2287 unlink($packages_list_under_construction);
2288 return;
2289 };
2291 # Read lines
2292 while ($line = <CONFIG>){
2293 # Unify
2294 chop($line);
2295 $line =~ s/^\s+//;
2296 $line =~ s/^\s+/ /;
2298 # Strip comments
2299 $line =~ s/#.*$//g;
2301 # Skip empty lines
2302 if ($line =~ /^\s*$/){
2303 next;
2304 }
2306 # Interpret deb line
2307 if ($line =~ /^deb [^\s]+\s[^\s]+\s[^\s]+/){
2308 my( $baseurl, $dist, $sections ) = ($line =~ /^deb\s([^\s]+)\s+([^\s]+)\s+(.*)$/);
2309 my $section;
2310 foreach $section (split(' ', $sections)){
2311 &parse_package_info( $baseurl, $dist, $section, $session_id );
2312 }
2313 }
2314 }
2316 close (CONFIG);
2318 find(\&cleanup_and_extract, keys( %repo_dirs ));
2319 &main::strip_packages_list_statements();
2320 unshift @packages_list_statements, "VACUUM";
2321 $packages_list_db->exec_statementlist(\@packages_list_statements);
2322 unlink($packages_list_under_construction);
2323 daemon_log("$session_id INFO: create_packages_list_db: finished", 5);
2324 return;
2325 }
2327 # This function should do some intensive task to minimize the db-traffic
2328 sub strip_packages_list_statements {
2329 my @existing_entries= @{$packages_list_db->exec_statement("SELECT * FROM $main::packages_list_tn")};
2330 my @new_statement_list=();
2331 my $hash;
2332 my $insert_hash;
2333 my $update_hash;
2334 my $delete_hash;
2335 my $local_timestamp=get_time();
2337 foreach my $existing_entry (@existing_entries) {
2338 $hash->{@{$existing_entry}[0]}->{@{$existing_entry}[1]}->{@{$existing_entry}[2]}= $existing_entry;
2339 }
2341 foreach my $statement (@packages_list_statements) {
2342 if($statement =~ /^INSERT/i) {
2343 # Assign the values from the insert statement
2344 my ($distribution,$package,$version,$section,$description,$template,$timestamp) = ($1,$2,$3,$4,$5,$6,$7) if $statement =~
2345 /^INSERT\s+?INTO\s+?$main::packages_list_tn\s+?VALUES\s*?\('(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)'\s*?\)$/si;
2346 if(exists($hash->{$distribution}->{$package}->{$version})) {
2347 # If section or description has changed, update the DB
2348 if(
2349 (! (@{$hash->{$distribution}->{$package}->{$version}}[3] eq $section)) or
2350 (! (@{$hash->{$distribution}->{$package}->{$version}}[4] eq $description))
2351 ) {
2352 @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,undef);
2353 }
2354 } else {
2355 # Insert a non-existing entry to db
2356 @{$insert_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
2357 }
2358 } elsif ($statement =~ /^UPDATE/i) {
2359 my ($template,$package,$version) = ($1,$2,$3) if $statement =~
2360 /^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;
2361 foreach my $distribution (keys %{$hash}) {
2362 if(exists($insert_hash->{$distribution}->{$package}->{$version})) {
2363 # update the insertion hash to execute only one query per package (insert instead insert+update)
2364 @{$insert_hash->{$distribution}->{$package}->{$version}}[5]= $template;
2365 } elsif(exists($hash->{$distribution}->{$package}->{$version})) {
2366 if( ! (@{$hash->{$distribution}->{$package}->{$version}}[5] eq $template)) {
2367 my $section;
2368 my $description;
2369 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) and
2370 length(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) > 0 ) {
2371 $section= @{$update_hash->{$distribution}->{$package}->{$version}}[3];
2372 }
2373 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
2374 $description= @{$update_hash->{$distribution}->{$package}->{$version}}[4];
2375 }
2376 @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
2377 }
2378 }
2379 }
2380 }
2381 }
2383 # TODO: Check for orphaned entries
2385 # unroll the insert_hash
2386 foreach my $distribution (keys %{$insert_hash}) {
2387 foreach my $package (keys %{$insert_hash->{$distribution}}) {
2388 foreach my $version (keys %{$insert_hash->{$distribution}->{$package}}) {
2389 push @new_statement_list, "INSERT INTO $main::packages_list_tn VALUES ('$distribution','$package','$version',"
2390 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[3]',"
2391 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[4]',"
2392 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[5]',"
2393 ."'$local_timestamp')";
2394 }
2395 }
2396 }
2398 # unroll the update hash
2399 foreach my $distribution (keys %{$update_hash}) {
2400 foreach my $package (keys %{$update_hash->{$distribution}}) {
2401 foreach my $version (keys %{$update_hash->{$distribution}->{$package}}) {
2402 my $set = "";
2403 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3])) {
2404 $set .= "section = '@{$update_hash->{$distribution}->{$package}->{$version}}[3]', ";
2405 }
2406 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
2407 $set .= "description = '@{$update_hash->{$distribution}->{$package}->{$version}}[4]', ";
2408 }
2409 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[5])) {
2410 $set .= "template = '@{$update_hash->{$distribution}->{$package}->{$version}}[5]', ";
2411 }
2412 if(defined($set) and length($set) > 0) {
2413 $set .= "timestamp = '$local_timestamp'";
2414 } else {
2415 next;
2416 }
2417 push @new_statement_list,
2418 "UPDATE $main::packages_list_tn SET $set WHERE"
2419 ." distribution = '$distribution'"
2420 ." AND package = '$package'"
2421 ." AND version = '$version'";
2422 }
2423 }
2424 }
2426 @packages_list_statements = @new_statement_list;
2427 }
2430 sub parse_package_info {
2431 my ($baseurl, $dist, $section, $session_id)= @_;
2432 my ($package);
2433 if (not defined $session_id) { $session_id = 0; }
2434 my ($path) = ($baseurl =~ m%://[^/]*(.*)$%);
2435 $repo_dirs{ "${repo_path}/pool" } = 1;
2437 foreach $package ("Packages.gz"){
2438 daemon_log("$session_id DEBUG: create_packages_list: fetch $baseurl, $dist, $section", 7);
2439 get_package( "$baseurl/dists/$dist/$section/binary-$arch/$package", "$outdir/$dist/$section", $session_id );
2440 parse_package( "$outdir/$dist/$section", $dist, $path, $session_id );
2441 }
2443 }
2446 sub get_package {
2447 my ($url, $dest, $session_id)= @_;
2448 if (not defined $session_id) { $session_id = 0; }
2450 my $tpath = dirname($dest);
2451 -d "$tpath" || mkpath "$tpath";
2453 # This is ugly, but I've no time to take a look at "how it works in perl"
2454 if(0 == system("wget '$url' -O '$dest' 2>/dev/null") ) {
2455 system("gunzip -cd '$dest' > '$dest.in'");
2456 daemon_log("$session_id DEBUG: run command: gunzip -cd '$dest' > '$dest.in'", 5);
2457 unlink($dest);
2458 daemon_log("$session_id DEBUG: delete file '$dest'", 5);
2459 } else {
2460 daemon_log("$session_id ERROR: create_packages_list_db: get_packages: fetching '$url' failed!", 1);
2461 }
2462 return 0;
2463 }
2466 sub parse_package {
2467 my ($path, $dist, $srv_path, $session_id)= @_;
2468 if (not defined $session_id) { $session_id = 0;}
2469 my ($package, $version, $section, $description);
2470 my $PACKAGES;
2471 my $timestamp = &get_time();
2473 if(not stat("$path.in")) {
2474 daemon_log("$session_id ERROR: create_packages_list: parse_package: file '$path.in' is not readable",1);
2475 return;
2476 }
2478 open($PACKAGES, "<$path.in");
2479 if(not defined($PACKAGES)) {
2480 daemon_log("$session_id ERROR: create_packages_list_db: parse_package: cannot open '$path.in'",1);
2481 return;
2482 }
2484 # Read lines
2485 while (<$PACKAGES>){
2486 my $line = $_;
2487 # Unify
2488 chop($line);
2490 # Use empty lines as a trigger
2491 if ($line =~ /^\s*$/){
2492 my $sql = "INSERT INTO packages_list VALUES ('$dist', '$package', '$version', '$section', '$description', '', '$timestamp')";
2493 push(@packages_list_statements, $sql);
2494 $package = "none";
2495 $version = "none";
2496 $section = "none";
2497 $description = "none";
2498 next;
2499 }
2501 # Trigger for package name
2502 if ($line =~ /^Package:\s/){
2503 ($package)= ($line =~ /^Package: (.*)$/);
2504 next;
2505 }
2507 # Trigger for version
2508 if ($line =~ /^Version:\s/){
2509 ($version)= ($line =~ /^Version: (.*)$/);
2510 next;
2511 }
2513 # Trigger for description
2514 if ($line =~ /^Description:\s/){
2515 ($description)= &encode_base64(($line =~ /^Description: (.*)$/));
2516 next;
2517 }
2519 # Trigger for section
2520 if ($line =~ /^Section:\s/){
2521 ($section)= ($line =~ /^Section: (.*)$/);
2522 next;
2523 }
2525 # Trigger for filename
2526 if ($line =~ /^Filename:\s/){
2527 my ($filename) = ($line =~ /^Filename: (.*)$/);
2528 store_fileinfo( $package, $filename, $dist, $srv_path, $version, $repo_path );
2529 next;
2530 }
2531 }
2533 close( $PACKAGES );
2534 unlink( "$path.in" );
2535 &main::daemon_log("$session_id DEBUG: unlink '$path.in'", 1);
2536 }
2539 sub store_fileinfo {
2540 my( $package, $file, $dist, $path, $vers, $srvdir) = @_;
2542 my %fileinfo = (
2543 'package' => $package,
2544 'dist' => $dist,
2545 'version' => $vers,
2546 );
2548 $repo_files{ "${srvdir}/$file" } = \%fileinfo;
2549 }
2552 sub cleanup_and_extract {
2553 my $fileinfo = $repo_files{ $File::Find::name };
2555 if( defined $fileinfo ) {
2557 my $dir = "$outdir/$fileinfo->{ 'dist' }/debconf.d";
2558 my $sql;
2559 my $package = $fileinfo->{ 'package' };
2560 my $newver = $fileinfo->{ 'version' };
2562 mkpath($dir);
2563 system( "dpkg -e '$File::Find::name' '$dir/DEBIAN'" );
2565 if( -f "$dir/DEBIAN/templates" ) {
2567 daemon_log("DEBUG: Found debconf templates in '$package' - $newver", 5);
2569 my $tmpl= "";
2570 {
2571 local $/=undef;
2572 open FILE, "$dir/DEBIAN/templates";
2573 $tmpl = &encode_base64(<FILE>);
2574 close FILE;
2575 }
2576 rmtree("$dir/DEBIAN/templates");
2578 $sql= "update $main::packages_list_tn set template = '$tmpl' where package = '$package' and version = '$newver';";
2579 push @packages_list_statements, $sql;
2580 }
2581 }
2583 return;
2584 }
2587 sub register_at_foreign_servers {
2588 my ($kernel) = $_[KERNEL];
2590 # hole alle bekannten server aus known_server_db
2591 my $server_sql = "SELECT * FROM $known_server_tn";
2592 my $server_res = $known_server_db->exec_statement($server_sql);
2594 # no entries in known_server_db
2595 if (not ref(@$server_res[0]) eq "ARRAY") {
2596 # TODO
2597 }
2599 # detect already connected clients
2600 my $client_sql = "SELECT * FROM $known_clients_tn";
2601 my $client_res = $known_clients_db->exec_statement($client_sql);
2603 # send my server details to all other gosa-si-server within the network
2604 foreach my $hit (@$server_res) {
2605 my $hostname = @$hit[0];
2606 my $hostkey = &create_passwd;
2608 # add already connected clients to registration message
2609 my $myhash = &create_xml_hash('new_server', $server_address, $hostname);
2610 &add_content2xml_hash($myhash, 'key', $hostkey);
2611 map(&add_content2xml_hash($myhash, 'client', @{$_}[0].",".@{$_}[4]), @$client_res);
2613 # build registration message and send it
2614 my $foreign_server_msg = &create_xml_string($myhash);
2615 my $error = &send_msg_to_target($foreign_server_msg, $hostname, $ServerPackages_key, "new_server", 0);
2616 }
2618 $kernel->delay_set("register_at_foreign_servers", $foreign_servers_register_delay);
2619 return;
2620 }
2623 #==== MAIN = main ==============================================================
2624 # parse commandline options
2625 Getopt::Long::Configure( "bundling" );
2626 GetOptions("h|help" => \&usage,
2627 "c|config=s" => \$cfg_file,
2628 "f|foreground" => \$foreground,
2629 "v|verbose+" => \$verbose,
2630 "no-bus+" => \$no_bus,
2631 "no-arp+" => \$no_arp,
2632 );
2634 # read and set config parameters
2635 &check_cmdline_param ;
2636 &read_configfile;
2637 &check_pid;
2639 $SIG{CHLD} = 'IGNORE';
2641 # forward error messages to logfile
2642 if( ! $foreground ) {
2643 open( STDIN, '+>/dev/null' );
2644 open( STDOUT, '+>&STDIN' );
2645 open( STDERR, '+>&STDIN' );
2646 }
2648 # Just fork, if we are not in foreground mode
2649 if( ! $foreground ) {
2650 chdir '/' or die "Can't chdir to /: $!";
2651 $pid = fork;
2652 setsid or die "Can't start a new session: $!";
2653 umask 0;
2654 } else {
2655 $pid = $$;
2656 }
2658 # Do something useful - put our PID into the pid_file
2659 if( 0 != $pid ) {
2660 open( LOCK_FILE, ">$pid_file" );
2661 print LOCK_FILE "$pid\n";
2662 close( LOCK_FILE );
2663 if( !$foreground ) {
2664 exit( 0 )
2665 };
2666 }
2668 # parse head url and revision from svn
2669 my $server_status_hash = { 'developmental'=>'revision', 'stable'=>'release'};
2670 $server_version =~ /^\$HeadURL: (\S+) \$:\$Rev: (\d+) \$$/;
2671 $server_headURL = defined $1 ? $1 : 'unknown' ;
2672 $server_revision = defined $2 ? $2 : 'unknown' ;
2673 if ($server_headURL =~ /\/tag\// ||
2674 $server_headURL =~ /\/branches\// ) {
2675 $server_status = "stable";
2676 } else {
2677 $server_status = "developmental" ;
2678 }
2681 daemon_log(" ", 1);
2682 daemon_log("$0 started!", 1);
2683 daemon_log("status: $server_status", 1);
2684 daemon_log($server_status_hash->{$server_status}.": $server_revision", 1);
2686 if ($no_bus > 0) {
2687 $bus_activ = "false"
2688 }
2690 # connect to incoming_db
2691 unlink($incoming_file_name);
2692 $incoming_db = GOSA::DBsqlite->new($incoming_file_name);
2693 $incoming_db->create_table($incoming_tn, \@incoming_col_names);
2695 # connect to gosa-si job queue
2696 $job_db = GOSA::DBsqlite->new($job_queue_file_name);
2697 $job_db->create_table($job_queue_tn, \@job_queue_col_names);
2699 # connect to known_clients_db
2700 $known_clients_db = GOSA::DBsqlite->new($known_clients_file_name);
2701 $known_clients_db->create_table($known_clients_tn, \@known_clients_col_names);
2703 # connect to foreign_clients_db
2704 $foreign_clients_db = GOSA::DBsqlite->new($foreign_clients_file_name);
2705 $foreign_clients_db->create_table($foreign_clients_tn, \@foreign_clients_col_names);
2707 # connect to known_server_db
2708 unlink($known_server_file_name);
2709 $known_server_db = GOSA::DBsqlite->new($known_server_file_name);
2710 $known_server_db->create_table($known_server_tn, \@known_server_col_names);
2712 # connect to login_usr_db
2713 $login_users_db = GOSA::DBsqlite->new($login_users_file_name);
2714 $login_users_db->create_table($login_users_tn, \@login_users_col_names);
2716 # connect to fai_server_db and fai_release_db
2717 unlink($fai_server_file_name);
2718 $fai_server_db = GOSA::DBsqlite->new($fai_server_file_name);
2719 $fai_server_db->create_table($fai_server_tn, \@fai_server_col_names);
2721 unlink($fai_release_file_name);
2722 $fai_release_db = GOSA::DBsqlite->new($fai_release_file_name);
2723 $fai_release_db->create_table($fai_release_tn, \@fai_release_col_names);
2725 # connect to packages_list_db
2726 #unlink($packages_list_file_name);
2727 unlink($packages_list_under_construction);
2728 $packages_list_db = GOSA::DBsqlite->new($packages_list_file_name);
2729 $packages_list_db->create_table($packages_list_tn, \@packages_list_col_names);
2731 # connect to messaging_db
2732 $messaging_db = GOSA::DBsqlite->new($messaging_file_name);
2733 $messaging_db->create_table($messaging_tn, \@messaging_col_names);
2736 # create xml object used for en/decrypting
2737 $xml = new XML::Simple();
2740 # foreign servers
2741 my @foreign_server_list;
2743 # add foreign server from cfg file
2744 if ($foreign_server_string ne "") {
2745 my @cfg_foreign_server_list = split(",", $foreign_server_string);
2746 foreach my $foreign_server (@cfg_foreign_server_list) {
2747 push(@foreign_server_list, $foreign_server);
2748 }
2749 }
2751 # add foreign server from dns
2752 my @tmp_servers;
2753 if ( !$server_domain) {
2754 # Try our DNS Searchlist
2755 for my $domain(get_dns_domains()) {
2756 chomp($domain);
2757 my @tmp_domains= &get_server_addresses($domain);
2758 if(@tmp_domains) {
2759 for my $tmp_server(@tmp_domains) {
2760 push @tmp_servers, $tmp_server;
2761 }
2762 }
2763 }
2764 if(@tmp_servers && length(@tmp_servers)==0) {
2765 daemon_log("0 WARNING: no foreign gosa-si-server found in DNS for domain '$server_domain'", 3);
2766 }
2767 } else {
2768 @tmp_servers = &get_server_addresses($server_domain);
2769 if( 0 == @tmp_servers ) {
2770 daemon_log("0 WARNING: no foreign gosa-si-server found in DNS for domain '$server_domain'", 3);
2771 }
2772 }
2773 foreach my $server (@tmp_servers) {
2774 unshift(@foreign_server_list, $server);
2775 }
2776 # eliminate duplicate entries
2777 @foreign_server_list = &del_doubles(@foreign_server_list);
2778 my $all_foreign_server = join(", ", @foreign_server_list);
2779 daemon_log("0 INFO: found foreign server in config file and DNS: $all_foreign_server", 5);
2781 # add all found foreign servers to known_server
2782 my $act_timestamp = &get_time();
2783 foreach my $foreign_server (@foreign_server_list) {
2784 my $res = $known_server_db->add_dbentry( {table=>$known_server_tn,
2785 primkey=>['hostname'],
2786 hostname=>$foreign_server,
2787 status=>'not_jet_registered',
2788 hostkey=>"none",
2789 timestamp=>$act_timestamp,
2790 } );
2791 }
2794 POE::Component::Server::TCP->new(
2795 Port => $server_port,
2796 ClientInput => sub {
2797 my ($kernel, $input) = @_[KERNEL, ARG0];
2798 push(@tasks, $input);
2799 push(@msgs_to_decrypt, $input);
2800 $kernel->yield("msg_to_decrypt");
2801 $kernel->yield("next_task");
2802 },
2803 InlineStates => {
2804 next_task => \&next_task,
2805 msg_to_decrypt => \&msg_to_decrypt,
2806 task_result => \&handle_task_result,
2807 task_done => \&handle_task_done,
2808 task_debug => \&handle_task_debug,
2809 child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!" },
2810 }
2811 );
2813 daemon_log("start socket for incoming xml messages at port '$server_port' ", 1);
2815 # create session for repeatedly checking the job queue for jobs
2816 POE::Session->create(
2817 inline_states => {
2818 _start => \&_start,
2819 register_at_foreign_servers => \®ister_at_foreign_servers,
2820 sig_handler => \&sig_handler,
2821 watch_for_new_messages => \&watch_for_new_messages,
2822 watch_for_delivery_messages => \&watch_for_delivery_messages,
2823 watch_for_done_messages => \&watch_for_done_messages,
2824 watch_for_new_jobs => \&watch_for_new_jobs,
2825 watch_for_done_jobs => \&watch_for_done_jobs,
2826 create_packages_list_db => \&run_create_packages_list_db,
2827 create_fai_server_db => \&run_create_fai_server_db,
2828 create_fai_release_db => \&run_create_fai_release_db,
2829 session_run_result => \&session_run_result,
2830 session_run_debug => \&session_run_debug,
2831 session_run_done => \&session_run_done,
2832 child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!" },
2833 }
2834 );
2837 # import all modules
2838 &import_modules;
2840 # TODO
2841 # check wether all modules are gosa-si valid passwd check
2845 POE::Kernel->run();
2846 exit;