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);
57 use DateTime;
59 my $modules_path = "/usr/lib/gosa-si/modules";
60 use lib "/usr/lib/gosa-si/modules";
62 # revision number of server and program name
63 my $server_version = '$HeadURL: https://oss.gonicus.de/repositories/gosa/trunk/gosa-si/gosa-si-server $:$Rev: 10826 $';
64 my $server_headURL;
65 my $server_revision;
66 my $server_status;
67 our $prg= basename($0);
69 our $global_kernel;
70 my ($foreground, $ping_timeout);
71 my ($bus_activ, $bus, $msg_to_bus, $bus_cipher);
72 my ($server);
73 my ($gosa_server, $job_queue_timeout, $job_queue_loop_delay);
74 my ($messaging_db_loop_delay);
75 my ($known_modules);
76 my ($procid, $pid);
77 my ($arp_fifo);
78 my ($xml);
79 my $sources_list;
80 my $max_clients;
81 my %repo_files=();
82 my $repo_path;
83 my %repo_dirs=();
84 # variables declared in config file are always set to 'our'
85 our (%cfg_defaults, $log_file, $pid_file,
86 $server_ip, $server_port, $ClientPackages_key,
87 $arp_activ, $gosa_unit_tag,
88 $GosaPackages_key, $gosa_ip, $gosa_port, $gosa_timeout,
89 $foreign_server_string, $server_domain, $ServerPackages_key, $foreign_servers_register_delay,
90 );
92 # additional variable which should be globaly accessable
93 our $server_address;
94 our $server_mac_address;
95 our $bus_address;
96 our $gosa_address;
97 our $no_bus;
98 our $no_arp;
99 our $verbose;
100 our $forground;
101 our $cfg_file;
102 our ($ldap_uri, $ldap_base, $ldap_admin_dn, $ldap_admin_password, $ldap_server_dn);
105 # specifies the verbosity of the daemon_log
106 $verbose = 0 ;
108 # if foreground is not null, script will be not forked to background
109 $foreground = 0 ;
111 # specifies the timeout seconds while checking the online status of a registrating client
112 $ping_timeout = 5;
114 $no_bus = 0;
115 $bus_activ = "true";
116 $no_arp = 0;
117 my $packages_list_under_construction = "/tmp/packages_list_creation_in_progress";
118 my @packages_list_statements;
119 my $watch_for_new_jobs_in_progress = 0;
121 # holds all incoming decrypted messages
122 our $incoming_db;
123 our $incoming_tn = 'incoming';
124 my $incoming_file_name;
125 my @incoming_col_names = ("id INTEGER PRIMARY KEY",
126 "timestamp DEFAULT 'none'",
127 "headertag DEFAULT 'none'",
128 "targettag DEFAULT 'none'",
129 "xmlmessage DEFAULT 'none'",
130 "module DEFAULT 'none'",
131 "sessionid DEFAULT '0'",
132 );
134 # holds all gosa jobs
135 our $job_db;
136 our $job_queue_tn = 'jobs';
137 my $job_queue_file_name;
138 my @job_queue_col_names = ("id INTEGER PRIMARY KEY",
139 "timestamp DEFAULT 'none'",
140 "status DEFAULT 'none'",
141 "result DEFAULT 'none'",
142 "progress DEFAULT 'none'",
143 "headertag DEFAULT 'none'",
144 "targettag DEFAULT 'none'",
145 "xmlmessage DEFAULT 'none'",
146 "macaddress DEFAULT 'none'",
147 "plainname DEFAULT 'none'",
148 );
150 # holds all other gosa-sd as well as the gosa-sd-bus
151 our $known_server_db;
152 our $known_server_tn = "known_server";
153 my $known_server_file_name;
154 my @known_server_col_names = ("hostname", "status", "hostkey", "timestamp");
156 # holds all registrated clients
157 our $known_clients_db;
158 our $known_clients_tn = "known_clients";
159 my $known_clients_file_name;
160 my @known_clients_col_names = ("hostname", "status", "hostkey", "timestamp", "macaddress", "events", "keylifetime");
162 # holds all registered clients at a foreign server
163 our $foreign_clients_db;
164 our $foreign_clients_tn = "foreign_clients";
165 my $foreign_clients_file_name;
166 my @foreign_clients_col_names = ("hostname", "macaddress", "regserver", "timestamp");
168 # holds all logged in user at each client
169 our $login_users_db;
170 our $login_users_tn = "login_users";
171 my $login_users_file_name;
172 my @login_users_col_names = ("client", "user", "timestamp");
174 # holds all fai server, the debian release and tag
175 our $fai_server_db;
176 our $fai_server_tn = "fai_server";
177 my $fai_server_file_name;
178 our @fai_server_col_names = ("timestamp", "server", "release", "sections", "tag");
180 our $fai_release_db;
181 our $fai_release_tn = "fai_release";
182 my $fai_release_file_name;
183 our @fai_release_col_names = ("timestamp", "release", "class", "type", "state");
185 # holds all packages available from different repositories
186 our $packages_list_db;
187 our $packages_list_tn = "packages_list";
188 my $packages_list_file_name;
189 our @packages_list_col_names = ("distribution", "package", "version", "section", "description", "template", "timestamp");
190 my $outdir = "/tmp/packages_list_db";
191 my $arch = "i386";
193 # holds all messages which should be delivered to a user
194 our $messaging_db;
195 our $messaging_tn = "messaging";
196 our @messaging_col_names = ("id INTEGER", "subject", "message_from", "message_to",
197 "flag", "direction", "delivery_time", "message", "timestamp" );
198 my $messaging_file_name;
200 # path to directory to store client install log files
201 our $client_fai_log_dir = "/var/log/fai";
203 # queue which stores taskes until one of the $max_children children are ready to process the task
204 my @tasks = qw();
205 my @msgs_to_decrypt = qw();
206 my $max_children = 2;
209 %cfg_defaults = (
210 "general" => {
211 "log-file" => [\$log_file, "/var/run/".$prg.".log"],
212 "pid-file" => [\$pid_file, "/var/run/".$prg.".pid"],
213 },
214 "bus" => {
215 "activ" => [\$bus_activ, "true"],
216 },
217 "server" => {
218 "port" => [\$server_port, "20081"],
219 "known-clients" => [\$known_clients_file_name, '/var/lib/gosa-si/clients.db' ],
220 "known-servers" => [\$known_server_file_name, '/var/lib/gosa-si/servers.db'],
221 "incoming" => [\$incoming_file_name, '/var/lib/gosa-si/incoming.db'],
222 "login-users" => [\$login_users_file_name, '/var/lib/gosa-si/users.db'],
223 "fai-server" => [\$fai_server_file_name, '/var/lib/gosa-si/fai_server.db'],
224 "fai-release" => [\$fai_release_file_name, '/var/lib/gosa-si/fai_release.db'],
225 "packages-list" => [\$packages_list_file_name, '/var/lib/gosa-si/packages.db'],
226 "messaging" => [\$messaging_file_name, '/var/lib/gosa-si/messaging.db'],
227 "foreign-clients" => [\$foreign_clients_file_name, '/var/lib/gosa-si/foreign_clients.db'],
228 "source-list" => [\$sources_list, '/etc/apt/sources.list'],
229 "repo-path" => [\$repo_path, '/srv/www/repository'],
230 "ldap-uri" => [\$ldap_uri, ""],
231 "ldap-base" => [\$ldap_base, ""],
232 "ldap-admin-dn" => [\$ldap_admin_dn, ""],
233 "ldap-admin-password" => [\$ldap_admin_password, ""],
234 "gosa-unit-tag" => [\$gosa_unit_tag, ""],
235 "max-clients" => [\$max_clients, 10],
236 },
237 "GOsaPackages" => {
238 "ip" => [\$gosa_ip, "0.0.0.0"],
239 "port" => [\$gosa_port, "20082"],
240 "job-queue" => [\$job_queue_file_name, '/var/lib/gosa-si/jobs.db'],
241 "job-queue-loop-delay" => [\$job_queue_loop_delay, 3],
242 "messaging-db-loop-delay" => [\$messaging_db_loop_delay, 3],
243 "key" => [\$GosaPackages_key, "none"],
244 },
245 "ClientPackages" => {
246 "key" => [\$ClientPackages_key, "none"],
247 },
248 "ServerPackages"=> {
249 "address" => [\$foreign_server_string, ""],
250 "domain" => [\$server_domain, ""],
251 "key" => [\$ServerPackages_key, "none"],
252 "key-lifetime" => [\$foreign_servers_register_delay, 120],
253 }
254 );
257 #=== FUNCTION ================================================================
258 # NAME: usage
259 # PARAMETERS: nothing
260 # RETURNS: nothing
261 # DESCRIPTION: print out usage text to STDERR
262 #===============================================================================
263 sub usage {
264 print STDERR << "EOF" ;
265 usage: $prg [-hvf] [-c config]
267 -h : this (help) message
268 -c <file> : config file
269 -f : foreground, process will not be forked to background
270 -v : be verbose (multiple to increase verbosity)
271 -no-bus : starts $prg without connection to bus
272 -no-arp : starts $prg without connection to arp module
274 EOF
275 print "\n" ;
276 }
279 #=== FUNCTION ================================================================
280 # NAME: read_configfile
281 # PARAMETERS: cfg_file - string -
282 # RETURNS: nothing
283 # DESCRIPTION: read cfg_file and set variables
284 #===============================================================================
285 sub read_configfile {
286 my $cfg;
287 if( defined( $cfg_file) && ( (-s $cfg_file) > 0 )) {
288 if( -r $cfg_file ) {
289 $cfg = Config::IniFiles->new( -file => $cfg_file );
290 } else {
291 print STDERR "Couldn't read config file!\n";
292 }
293 } else {
294 $cfg = Config::IniFiles->new() ;
295 }
296 foreach my $section (keys %cfg_defaults) {
297 foreach my $param (keys %{$cfg_defaults{ $section }}) {
298 my $pinfo = $cfg_defaults{ $section }{ $param };
299 ${@$pinfo[ 0 ]} = $cfg->val( $section, $param, @$pinfo[ 1 ] );
300 }
301 }
302 }
305 #=== FUNCTION ================================================================
306 # NAME: logging
307 # PARAMETERS: level - string - default 'info'
308 # msg - string -
309 # facility - string - default 'LOG_DAEMON'
310 # RETURNS: nothing
311 # DESCRIPTION: function for logging
312 #===============================================================================
313 sub daemon_log {
314 # log into log_file
315 my( $msg, $level ) = @_;
316 if(not defined $msg) { return }
317 if(not defined $level) { $level = 1 }
318 if(defined $log_file){
319 open(LOG_HANDLE, ">>$log_file");
320 chmod 0600, $log_file;
321 if(not defined open( LOG_HANDLE, ">>$log_file" )) {
322 print STDERR "cannot open $log_file: $!";
323 return }
324 chomp($msg);
325 $msg =~s/\n//g; # no newlines are allowed in log messages, this is important for later log parsing
326 if($level <= $verbose){
327 my ($seconds, $minutes, $hours, $monthday, $month,
328 $year, $weekday, $yearday, $sommertime) = localtime(time);
329 $hours = $hours < 10 ? $hours = "0".$hours : $hours;
330 $minutes = $minutes < 10 ? $minutes = "0".$minutes : $minutes;
331 $seconds = $seconds < 10 ? $seconds = "0".$seconds : $seconds;
332 my @monthnames = ("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec");
333 $month = $monthnames[$month];
334 $monthday = $monthday < 10 ? $monthday = "0".$monthday : $monthday;
335 $year+=1900;
336 my $name = $prg;
338 my $log_msg = "$month $monthday $hours:$minutes:$seconds $name $msg\n";
339 print LOG_HANDLE $log_msg;
340 if( $foreground ) {
341 print STDERR $log_msg;
342 }
343 }
344 close( LOG_HANDLE );
345 }
346 }
349 #=== FUNCTION ================================================================
350 # NAME: check_cmdline_param
351 # PARAMETERS: nothing
352 # RETURNS: nothing
353 # DESCRIPTION: validates commandline parameter
354 #===============================================================================
355 sub check_cmdline_param () {
356 my $err_config;
357 my $err_counter = 0;
358 if(not defined($cfg_file)) {
359 $cfg_file = "/etc/gosa-si/server.conf";
360 if(! -r $cfg_file) {
361 $err_config = "please specify a config file";
362 $err_counter += 1;
363 }
364 }
365 if( $err_counter > 0 ) {
366 &usage( "", 1 );
367 if( defined( $err_config)) { print STDERR "$err_config\n"}
368 print STDERR "\n";
369 exit( -1 );
370 }
371 }
374 #=== FUNCTION ================================================================
375 # NAME: check_pid
376 # PARAMETERS: nothing
377 # RETURNS: nothing
378 # DESCRIPTION: handels pid processing
379 #===============================================================================
380 sub check_pid {
381 $pid = -1;
382 # Check, if we are already running
383 if( open(LOCK_FILE, "<$pid_file") ) {
384 $pid = <LOCK_FILE>;
385 if( defined $pid ) {
386 chomp( $pid );
387 if( -f "/proc/$pid/stat" ) {
388 my($stat) = `cat /proc/$pid/stat` =~ m/$pid \((.+)\).*/;
389 if( $stat ) {
390 daemon_log("ERROR: Already running",1);
391 close( LOCK_FILE );
392 exit -1;
393 }
394 }
395 }
396 close( LOCK_FILE );
397 unlink( $pid_file );
398 }
400 # create a syslog msg if it is not to possible to open PID file
401 if (not sysopen(LOCK_FILE, $pid_file, O_WRONLY|O_CREAT|O_EXCL, 0644)) {
402 my($msg) = "Couldn't obtain lockfile '$pid_file' ";
403 if (open(LOCK_FILE, '<', $pid_file)
404 && ($pid = <LOCK_FILE>))
405 {
406 chomp($pid);
407 $msg .= "(PID $pid)\n";
408 } else {
409 $msg .= "(unable to read PID)\n";
410 }
411 if( ! ($foreground) ) {
412 openlog( $0, "cons,pid", "daemon" );
413 syslog( "warning", $msg );
414 closelog();
415 }
416 else {
417 print( STDERR " $msg " );
418 }
419 exit( -1 );
420 }
421 }
423 #=== FUNCTION ================================================================
424 # NAME: import_modules
425 # PARAMETERS: module_path - string - abs. path to the directory the modules
426 # are stored
427 # RETURNS: nothing
428 # DESCRIPTION: each file in module_path which ends with '.pm' and activation
429 # state is on is imported by "require 'file';"
430 #===============================================================================
431 sub import_modules {
432 daemon_log(" ", 1);
434 if (not -e $modules_path) {
435 daemon_log("0 ERROR: cannot find directory or directory is not readable: $modules_path", 1);
436 }
438 opendir (DIR, $modules_path) or die "ERROR while loading modules from directory $modules_path : $!\n";
439 while (defined (my $file = readdir (DIR))) {
440 if (not $file =~ /(\S*?).pm$/) {
441 next;
442 }
443 my $mod_name = $1;
445 if( $file =~ /ArpHandler.pm/ ) {
446 if( $no_arp > 0 ) {
447 next;
448 }
449 }
451 eval { require $file; };
452 if ($@) {
453 daemon_log("0 ERROR: gosa-si-server could not load module $file", 1);
454 daemon_log("$@", 5);
455 } else {
456 my $info = eval($mod_name.'::get_module_info()');
457 # Only load module if get_module_info() returns a non-null object
458 if( $info ) {
459 my ($input_address, $input_key, $input, $input_active, $input_type) = @{$info};
460 $known_modules->{$mod_name} = $info;
461 daemon_log("0 INFO: module $mod_name loaded", 5);
462 }
463 }
464 }
465 close (DIR);
466 }
469 #=== FUNCTION ================================================================
470 # NAME: sig_int_handler
471 # PARAMETERS: signal - string - signal arose from system
472 # RETURNS: noting
473 # DESCRIPTION: handels tasks to be done befor signal becomes active
474 #===============================================================================
475 sub sig_int_handler {
476 my ($signal) = @_;
478 # if (defined($ldap_handle)) {
479 # $ldap_handle->disconnect;
480 # }
481 # TODO alle verbliebenden ldap verbindungen aus allen heaps beenden
484 daemon_log("shutting down gosa-si-server", 1);
485 system("kill `ps -C gosa-si-server-nobus -o pid=`");
486 }
487 $SIG{INT} = \&sig_int_handler;
490 sub check_key_and_xml_validity {
491 my ($crypted_msg, $module_key, $session_id) = @_;
492 my $msg;
493 my $msg_hash;
494 my $error_string;
495 eval{
496 $msg = &decrypt_msg($crypted_msg, $module_key);
498 if ($msg =~ /<xml>/i){
499 $msg =~ s/\s+/ /g; # just for better daemon_log
500 daemon_log("$session_id DEBUG: decrypted_msg: \n$msg", 8);
501 $msg_hash = $xml->XMLin($msg, ForceArray=>1);
503 ##############
504 # check header
505 if( not exists $msg_hash->{'header'} ) { die "no header specified"; }
506 my $header_l = $msg_hash->{'header'};
507 if( 1 > @{$header_l} ) { die 'empty header tag'; }
508 if( 1 < @{$header_l} ) { die 'more than one header specified'; }
509 my $header = @{$header_l}[0];
510 if( 0 == length $header) { die 'empty string in header tag'; }
512 ##############
513 # check source
514 if( not exists $msg_hash->{'source'} ) { die "no source specified"; }
515 my $source_l = $msg_hash->{'source'};
516 if( 1 > @{$source_l} ) { die 'empty source tag'; }
517 if( 1 < @{$source_l} ) { die 'more than one source specified'; }
518 my $source = @{$source_l}[0];
519 if( 0 == length $source) { die 'source error'; }
521 ##############
522 # check target
523 if( not exists $msg_hash->{'target'} ) { die "no target specified"; }
524 my $target_l = $msg_hash->{'target'};
525 if( 1 > @{$target_l} ) { die 'empty target tag'; }
526 }
527 };
528 if($@) {
529 daemon_log("$session_id DEBUG: do not understand the message: $@", 7);
530 $msg = undef;
531 $msg_hash = undef;
532 }
534 return ($msg, $msg_hash);
535 }
538 sub check_outgoing_xml_validity {
539 my ($msg) = @_;
541 my $msg_hash;
542 eval{
543 $msg_hash = $xml->XMLin($msg, ForceArray=>1);
545 ##############
546 # check header
547 my $header_l = $msg_hash->{'header'};
548 if( 1 != @{$header_l} ) {
549 die 'no or more than one headers specified';
550 }
551 my $header = @{$header_l}[0];
552 if( 0 == length $header) {
553 die 'header has length 0';
554 }
556 ##############
557 # check source
558 my $source_l = $msg_hash->{'source'};
559 if( 1 != @{$source_l} ) {
560 die 'no or more than 1 sources specified';
561 }
562 my $source = @{$source_l}[0];
563 if( 0 == length $source) {
564 die 'source has length 0';
565 }
566 unless( $source =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
567 $source =~ /^GOSA$/i ) {
568 die "source '$source' is neither a complete ip-address with port nor 'GOSA'";
569 }
571 ##############
572 # check target
573 my $target_l = $msg_hash->{'target'};
574 if( 0 == @{$target_l} ) {
575 die "no targets specified";
576 }
577 foreach my $target (@$target_l) {
578 if( 0 == length $target) {
579 die "target has length 0";
580 }
581 unless( $target =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
582 $target =~ /^GOSA$/i ||
583 $target =~ /^\*$/ ||
584 $target =~ /KNOWN_SERVER/i ||
585 $target =~ /JOBDB/i ||
586 $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 ){
587 die "target '$target' is not a complete ip-address with port or a valid target name or a mac-address";
588 }
589 }
590 };
591 if($@) {
592 daemon_log("WARNING: outgoing msg is not gosa-si envelope conform", 5);
593 daemon_log("$@ ".(defined($msg) && length($msg)>0)?$msg:"Empty Message", 8);
594 $msg_hash = undef;
595 }
597 return ($msg_hash);
598 }
601 sub input_from_known_server {
602 my ($input, $remote_ip, $session_id) = @_ ;
603 my ($msg, $msg_hash, $module);
605 my $sql_statement= "SELECT * FROM known_server";
606 my $query_res = $known_server_db->select_dbentry( $sql_statement );
608 while( my ($hit_num, $hit) = each %{ $query_res } ) {
609 my $host_name = $hit->{hostname};
610 if( not $host_name =~ "^$remote_ip") {
611 next;
612 }
613 my $host_key = $hit->{hostkey};
614 daemon_log("$session_id DEBUG: input_from_known_server: host_name: $host_name", 7);
615 daemon_log("$session_id DEBUG: input_from_known_server: host_key: $host_key", 7);
617 # check if module can open msg envelope with module key
618 my ($tmp_msg, $tmp_msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
619 if( (!$tmp_msg) || (!$tmp_msg_hash) ) {
620 daemon_log("$session_id DEBUG: input_from_known_server: deciphering raise error", 7);
621 daemon_log("$@", 8);
622 next;
623 }
624 else {
625 $msg = $tmp_msg;
626 $msg_hash = $tmp_msg_hash;
627 $module = "ServerPackages";
628 last;
629 }
630 }
632 if( (!$msg) || (!$msg_hash) || (!$module) ) {
633 daemon_log("$session_id DEBUG: Incoming message is not from a known server", 7);
634 }
636 return ($msg, $msg_hash, $module);
637 }
640 sub input_from_known_client {
641 my ($input, $remote_ip, $session_id) = @_ ;
642 my ($msg, $msg_hash, $module);
644 my $sql_statement= "SELECT * FROM known_clients";
645 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
646 while( my ($hit_num, $hit) = each %{ $query_res } ) {
647 my $host_name = $hit->{hostname};
648 if( not $host_name =~ /^$remote_ip:\d*$/) {
649 next;
650 }
651 my $host_key = $hit->{hostkey};
652 &daemon_log("$session_id DEBUG: input_from_known_client: host_name: $host_name", 7);
653 &daemon_log("$session_id DEBUG: input_from_known_client: host_key: $host_key", 7);
655 # check if module can open msg envelope with module key
656 ($msg, $msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
658 if( (!$msg) || (!$msg_hash) ) {
659 &daemon_log("$session_id DEGUG: input_from_known_client: deciphering raise error", 7);
660 &daemon_log("$@", 8);
661 next;
662 }
663 else {
664 $module = "ClientPackages";
665 last;
666 }
667 }
669 if( (!$msg) || (!$msg_hash) || (!$module) ) {
670 &daemon_log("$session_id DEBUG: Incoming message is not from a known client", 7);
671 }
673 return ($msg, $msg_hash, $module);
674 }
677 sub input_from_unknown_host {
678 no strict "refs";
679 my ($input, $session_id) = @_ ;
680 my ($msg, $msg_hash, $module);
681 my $error_string;
683 my %act_modules = %$known_modules;
685 while( my ($mod, $info) = each(%act_modules)) {
687 # check a key exists for this module
688 my $module_key = ${$mod."_key"};
689 if( not defined $module_key ) {
690 if( $mod eq 'ArpHandler' ) {
691 next;
692 }
693 daemon_log("$session_id ERROR: no key specified in config file for $mod", 1);
694 next;
695 }
696 daemon_log("$session_id DEBUG: $mod: $module_key", 7);
698 # check if module can open msg envelope with module key
699 ($msg, $msg_hash) = &check_key_and_xml_validity($input, $module_key, $session_id);
700 if( (not defined $msg) || (not defined $msg_hash) ) {
701 next;
702 }
703 else {
704 $module = $mod;
705 last;
706 }
707 }
709 if( (!$msg) || (!$msg_hash) || (!$module)) {
710 daemon_log("$session_id DEBUG: Incoming message is not from an unknown host", 7);
711 }
713 return ($msg, $msg_hash, $module);
714 }
717 sub create_ciphering {
718 my ($passwd) = @_;
719 if((!defined($passwd)) || length($passwd)==0) {
720 $passwd = "";
721 }
722 $passwd = substr(md5_hex("$passwd") x 32, 0, 32);
723 my $iv = substr(md5_hex('GONICUS GmbH'),0, 16);
724 my $my_cipher = Crypt::Rijndael->new($passwd , Crypt::Rijndael::MODE_CBC());
725 $my_cipher->set_iv($iv);
726 return $my_cipher;
727 }
730 sub encrypt_msg {
731 my ($msg, $key) = @_;
732 my $my_cipher = &create_ciphering($key);
733 my $len;
734 {
735 use bytes;
736 $len= 16-length($msg)%16;
737 }
738 $msg = "\0"x($len).$msg;
739 $msg = $my_cipher->encrypt($msg);
740 chomp($msg = &encode_base64($msg));
741 # there are no newlines allowed inside msg
742 $msg=~ s/\n//g;
743 return $msg;
744 }
747 sub decrypt_msg {
749 my ($msg, $key) = @_ ;
750 $msg = &decode_base64($msg);
751 my $my_cipher = &create_ciphering($key);
752 $msg = $my_cipher->decrypt($msg);
753 $msg =~ s/\0*//g;
754 return $msg;
755 }
758 sub get_encrypt_key {
759 my ($target) = @_ ;
760 my $encrypt_key;
761 my $error = 0;
763 # target can be in known_server
764 if( not defined $encrypt_key ) {
765 my $sql_statement= "SELECT * FROM known_server WHERE hostname='$target'";
766 my $query_res = $known_server_db->select_dbentry( $sql_statement );
767 while( my ($hit_num, $hit) = each %{ $query_res } ) {
768 my $host_name = $hit->{hostname};
769 if( $host_name ne $target ) {
770 next;
771 }
772 $encrypt_key = $hit->{hostkey};
773 last;
774 }
775 }
777 # target can be in known_client
778 if( not defined $encrypt_key ) {
779 my $sql_statement= "SELECT * FROM known_clients WHERE hostname='$target'";
780 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
781 while( my ($hit_num, $hit) = each %{ $query_res } ) {
782 my $host_name = $hit->{hostname};
783 if( $host_name ne $target ) {
784 next;
785 }
786 $encrypt_key = $hit->{hostkey};
787 last;
788 }
789 }
791 return $encrypt_key;
792 }
795 #=== FUNCTION ================================================================
796 # NAME: open_socket
797 # PARAMETERS: PeerAddr string something like 192.168.1.1 or 192.168.1.1:10000
798 # [PeerPort] string necessary if port not appended by PeerAddr
799 # RETURNS: socket IO::Socket::INET
800 # DESCRIPTION: open a socket to PeerAddr
801 #===============================================================================
802 sub open_socket {
803 my ($PeerAddr, $PeerPort) = @_ ;
804 if(defined($PeerPort)){
805 $PeerAddr = $PeerAddr.":".$PeerPort;
806 }
807 my $socket;
808 $socket = new IO::Socket::INET(PeerAddr => $PeerAddr,
809 Porto => "tcp",
810 Type => SOCK_STREAM,
811 Timeout => 5,
812 );
813 if(not defined $socket) {
814 return;
815 }
816 # &daemon_log("DEBUG: open_socket: $PeerAddr", 7);
817 return $socket;
818 }
821 #=== FUNCTION ================================================================
822 # NAME: get_ip
823 # PARAMETERS: interface name (i.e. eth0)
824 # RETURNS: (ip address)
825 # DESCRIPTION: Uses ioctl to get ip address directly from system.
826 #===============================================================================
827 sub get_ip {
828 my $ifreq= shift;
829 my $result= "";
830 my $SIOCGIFADDR= 0x8915; # man 2 ioctl_list
831 my $proto= getprotobyname('ip');
833 socket SOCKET, PF_INET, SOCK_DGRAM, $proto
834 or die "socket: $!";
836 if(ioctl SOCKET, $SIOCGIFADDR, $ifreq) {
837 my ($if, $sin) = unpack 'a16 a16', $ifreq;
838 my ($port, $addr) = sockaddr_in $sin;
839 my $ip = inet_ntoa $addr;
841 if ($ip && length($ip) > 0) {
842 $result = $ip;
843 }
844 }
846 return $result;
847 }
850 sub get_local_ip_for_remote_ip {
851 my $remote_ip= shift;
852 my $result="0.0.0.0";
854 if($remote_ip =~ /^(\d\d?\d?\.){3}\d\d?\d?$/) {
855 if($remote_ip eq "127.0.0.1") {
856 $result = "127.0.0.1";
857 } else {
858 my $PROC_NET_ROUTE= ('/proc/net/route');
860 open(PROC_NET_ROUTE, "<$PROC_NET_ROUTE")
861 or die "Could not open $PROC_NET_ROUTE";
863 my @ifs = <PROC_NET_ROUTE>;
865 close(PROC_NET_ROUTE);
867 # Eat header line
868 shift @ifs;
869 chomp @ifs;
870 foreach my $line(@ifs) {
871 my ($Iface,$Destination,$Gateway,$Flags,$RefCnt,$Use,$Metric,$Mask,$MTU,$Window,$IRTT)=split(/\s/, $line);
872 my $destination;
873 my $mask;
874 my ($d,$c,$b,$a)=unpack('a2 a2 a2 a2', $Destination);
875 $destination= sprintf("%d.%d.%d.%d", hex($a), hex($b), hex($c), hex($d));
876 ($d,$c,$b,$a)=unpack('a2 a2 a2 a2', $Mask);
877 $mask= sprintf("%d.%d.%d.%d", hex($a), hex($b), hex($c), hex($d));
878 if(new NetAddr::IP($remote_ip)->within(new NetAddr::IP($destination, $mask))) {
879 # destination matches route, save mac and exit
880 $result= &get_ip($Iface);
881 last;
882 }
883 }
884 }
885 } else {
886 daemon_log("get_local_ip_for_remote_ip was called with a non-ip parameter: $remote_ip", 1);
887 }
888 return $result;
889 }
892 sub send_msg_to_target {
893 my ($msg, $address, $encrypt_key, $msg_header, $session_id) = @_ ;
894 my $error = 0;
895 my $header;
896 my $timestamp = &get_time();
897 my $new_status;
898 my $act_status;
899 my ($sql_statement, $res);
901 if( $msg_header ) {
902 $header = "'$msg_header'-";
903 } else {
904 $header = "";
905 }
907 # Patch the source ip
908 if($msg =~ /<source>0\.0\.0\.0:\d*?<\/source>/) {
909 my $remote_ip = &get_local_ip_for_remote_ip(sprintf("%s", $address =~ /^([0-9\.]*?):.*$/));
910 $msg =~ s/<source>(0\.0\.0\.0):(\d*?)<\/source>/<source>$remote_ip:$2<\/source>/s;
911 }
913 # encrypt xml msg
914 my $crypted_msg = &encrypt_msg($msg, $encrypt_key);
916 # opensocket
917 my $socket = &open_socket($address);
918 if( !$socket ) {
919 daemon_log("$session_id ERROR: cannot send ".$header."msg to $address , host not reachable", 1);
920 $error++;
921 }
923 if( $error == 0 ) {
924 # send xml msg
925 print $socket $crypted_msg."\n";
927 daemon_log("$session_id INFO: send ".$header."msg to $address", 5);
928 daemon_log("$session_id DEBUG: message:\n$msg", 9);
930 }
932 # close socket in any case
933 if( $socket ) {
934 close $socket;
935 }
937 if( $error > 0 ) { $new_status = "down"; }
938 else { $new_status = $msg_header; }
941 # known_clients
942 $sql_statement = "SELECT * FROM known_clients WHERE hostname='$address'";
943 $res = $known_clients_db->select_dbentry($sql_statement);
944 if( keys(%$res) == 1) {
945 $act_status = $res->{1}->{'status'};
946 if ($act_status eq "down" && $new_status eq "down") {
947 $sql_statement = "DELETE FROM known_clients WHERE hostname='$address'";
948 $res = $known_clients_db->del_dbentry($sql_statement);
949 daemon_log("$session_id WARNING: failed 2x to send msg to host '$address', delete host from known_clients", 3);
950 } else {
951 $sql_statement = "UPDATE known_clients SET status='$new_status', timestamp='$timestamp' WHERE hostname='$address'";
952 $res = $known_clients_db->update_dbentry($sql_statement);
953 if($new_status eq "down"){
954 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
955 } else {
956 daemon_log("$session_id INFO: set '$address' from status '$act_status' to '$new_status'", 5);
957 }
958 }
959 }
961 # known_server
962 $sql_statement = "SELECT * FROM $known_server_tn WHERE hostname='$address'";
963 $res = $known_server_db->select_dbentry($sql_statement);
964 if( keys(%$res) == 1) {
965 $act_status = $res->{1}->{'status'};
966 if ($act_status eq "down" && $new_status eq "down") {
967 $sql_statement = "DELETE FROM known_server WHERE hostname='$address'";
968 $res = $known_server_db->del_dbentry($sql_statement);
969 daemon_log("$session_id WARNING: failed 2x to send a message to host '$address', delete host from known_server", 3);
970 }
971 else {
972 $sql_statement = "UPDATE known_server SET status='$new_status', timestamp='$timestamp' WHERE hostname='$address'";
973 $res = $known_server_db->update_dbentry($sql_statement);
974 if($new_status eq "down"){
975 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
976 }
977 else {
978 daemon_log("$session_id INFO: set '$address' from status '$act_status' to '$new_status'", 5);
979 }
980 }
981 }
982 return $error;
983 }
986 sub update_jobdb_status_for_send_msgs {
987 my ($answer, $error) = @_;
988 if( $answer =~ /<jobdb_id>(\d+)<\/jobdb_id>/ ) {
989 my $jobdb_id = $1;
991 # sending msg faild
992 if( $error ) {
993 if (not $answer =~ /<header>trigger_action_reinstall<\/header>/) {
994 my $sql_statement = "UPDATE $job_queue_tn ".
995 "SET status='error', result='can not deliver msg, please consult log file' ".
996 "WHERE id=$jobdb_id";
997 my $res = $job_db->update_dbentry($sql_statement);
998 }
1000 # sending msg was successful
1001 } else {
1002 my $sql_statement = "UPDATE $job_queue_tn ".
1003 "SET status='done' ".
1004 "WHERE id=$jobdb_id AND status='processed'";
1005 my $res = $job_db->update_dbentry($sql_statement);
1006 }
1007 }
1008 }
1011 sub sig_handler {
1012 my ($kernel, $signal) = @_[KERNEL, ARG0] ;
1013 daemon_log("0 INFO got signal '$signal'", 1);
1014 $kernel->sig_handled();
1015 return;
1016 }
1019 sub msg_to_decrypt {
1020 my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
1021 my $session_id = $session->ID;
1022 my ($msg, $msg_hash, $module);
1023 my $error = 0;
1025 # hole neue msg aus @msgs_to_decrypt
1026 my $next_msg = shift @msgs_to_decrypt;
1028 # entschlüssle sie
1030 # msg is from a new client or gosa
1031 ($msg, $msg_hash, $module) = &input_from_unknown_host($next_msg, $session_id);
1032 # msg is from a gosa-si-server or gosa-si-bus
1033 if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1034 ($msg, $msg_hash, $module) = &input_from_known_server($next_msg, $heap->{'remote_ip'}, $session_id);
1035 }
1036 # msg is from a gosa-si-client
1037 if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1038 ($msg, $msg_hash, $module) = &input_from_known_client($next_msg, $heap->{'remote_ip'}, $session_id);
1039 }
1040 # an error occurred
1041 if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1042 # if an incoming msg could not be decrypted (maybe a wrong key), send client a ping. If the client
1043 # could not understand a msg from its server the client cause a re-registering process
1044 daemon_log("$session_id INFO cannot understand incoming msg, send 'ping'-msg to all host with ip '".$heap->{remote_ip}."' to cause a re-registering of the client if necessary", 5);
1045 my $sql_statement = "SELECT * FROM $main::known_clients_tn WHERE (hostname LIKE '".$heap->{'remote_ip'}."%')";
1046 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1047 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1048 my $host_name = $hit->{'hostname'};
1049 my $host_key = $hit->{'hostkey'};
1050 my $ping_msg = "<xml> <header>gosa_ping</header> <source>$server_address</source> <target>$host_name</target></xml>";
1051 my $error = &send_msg_to_target($ping_msg, $host_name, $host_key, "gosa_ping", $session_id);
1052 &update_jobdb_status_for_send_msgs($ping_msg, $error);
1053 }
1054 $error++;
1055 }
1057 # add message to incoming_db
1058 if( $error == 0) {
1059 my $header = @{$msg_hash->{'header'}}[0];
1060 my $target = @{$msg_hash->{'target'}}[0];
1061 my $res = $incoming_db->add_dbentry( {table=>$incoming_tn,
1062 primkey=>[],
1063 headertag=>$header,
1064 targettag=>$target,
1065 xmlmessage=>$msg,
1066 timestamp=>&get_time,
1067 module=>$module,
1068 sessionid=>$session_id,
1069 } );
1070 if ($res != 0) {
1071 # TODO ist das mit $! so ok???
1072 #&daemon_log("$session_id ERROR: cannot add message to incoming.db: $!", 1);
1073 }
1074 }
1076 }
1079 sub next_task {
1080 my ($session, $heap, $task) = @_[SESSION, HEAP, ARG0];
1081 my $running_task = POE::Wheel::Run->new(
1082 Program => sub { process_task($session, $heap, $task) },
1083 StdioFilter => POE::Filter::Reference->new(),
1084 StdoutEvent => "task_result",
1085 StderrEvent => "task_debug",
1086 CloseEvent => "task_done",
1087 );
1088 $heap->{task}->{ $running_task->ID } = $running_task;
1089 }
1091 sub handle_task_result {
1092 my ($kernel, $heap, $result) = @_[KERNEL, HEAP, ARG0];
1093 my $client_answer = $result->{'answer'};
1094 if( $client_answer =~ s/session_id=(\d+)$// ) {
1095 my $session_id = $1;
1096 if( defined $session_id ) {
1097 my $session_reference = $kernel->ID_id_to_session($session_id);
1098 if( defined $session_reference ) {
1099 $heap = $session_reference->get_heap();
1100 }
1101 }
1103 if(exists $heap->{'client'}) {
1104 $heap->{'client'}->put($client_answer);
1105 }
1106 }
1107 $kernel->sig(CHLD => "child_reap");
1108 }
1110 sub handle_task_debug {
1111 my $result = $_[ARG0];
1112 print STDERR "$result\n";
1113 }
1115 sub handle_task_done {
1116 my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
1117 delete $heap->{task}->{$task_id};
1118 }
1120 sub process_task {
1121 no strict "refs";
1122 my ($session, $heap, $task) = @_;
1123 my $error = 0;
1124 my $answer_l;
1125 my ($answer_header, @answer_target_l, $answer_source);
1126 my $client_answer = "";
1128 # prepare all variables needed to process message
1129 my $msg = $task->{'xmlmessage'};
1130 my $incoming_id = $task->{'id'};
1131 my $module = $task->{'module'};
1132 my $header = $task->{'headertag'};
1133 my $session_id = $task->{'sessionid'};
1134 my $msg_hash = $xml->XMLin($msg, ForceArray=>1);
1135 my $source = @{$msg_hash->{'source'}}[0];
1137 # set timestamp of incoming client uptodate, so client will not
1138 # be deleted from known_clients because of expiration
1139 my $act_time = &get_time();
1140 my $sql = "UPDATE $known_clients_tn SET timestamp='$act_time' WHERE hostname='$source'";
1141 my $res = $known_clients_db->exec_statement($sql);
1143 ######################
1144 # process incoming msg
1145 if( $error == 0) {
1146 daemon_log("$session_id INFO: Incoming msg (session_id=$session_id) with header '".@{$msg_hash->{'header'}}[0]."'", 5);
1147 daemon_log("$session_id DEBUG: Processing module ".$module, 7);
1148 $answer_l = &{ $module."::process_incoming_msg" }($msg, $msg_hash, $session_id);
1150 if ( 0 < @{$answer_l} ) {
1151 my $answer_str = join("\n", @{$answer_l});
1152 while ($answer_str =~ /<header>(\w+)<\/header>/g) {
1153 daemon_log("$session_id INFO: got answer message with header '$1'", 5);
1154 }
1155 daemon_log("$session_id DEBUG: $module: got answer from module: \n".$answer_str,8);
1156 } else {
1157 daemon_log("$session_id DEBUG: $module: got no answer from module!" ,8);
1158 }
1160 }
1161 if( !$answer_l ) { $error++ };
1163 ########
1164 # answer
1165 if( $error == 0 ) {
1167 foreach my $answer ( @{$answer_l} ) {
1168 # check outgoing msg to xml validity
1169 my $answer_hash = &check_outgoing_xml_validity($answer);
1170 if( not defined $answer_hash ) { next; }
1172 $answer_header = @{$answer_hash->{'header'}}[0];
1173 @answer_target_l = @{$answer_hash->{'target'}};
1174 $answer_source = @{$answer_hash->{'source'}}[0];
1176 # deliver msg to all targets
1177 foreach my $answer_target ( @answer_target_l ) {
1179 # targets of msg are all gosa-si-clients in known_clients_db
1180 if( $answer_target eq "*" ) {
1181 # answer is for all clients
1182 my $sql_statement= "SELECT * FROM known_clients";
1183 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1184 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1185 my $host_name = $hit->{hostname};
1186 my $host_key = $hit->{hostkey};
1187 my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1188 &update_jobdb_status_for_send_msgs($answer, $error);
1189 }
1190 }
1192 # targets of msg are all gosa-si-server in known_server_db
1193 elsif( $answer_target eq "KNOWN_SERVER" ) {
1194 # answer is for all server in known_server
1195 my $sql_statement= "SELECT * FROM known_server";
1196 my $query_res = $known_server_db->select_dbentry( $sql_statement );
1197 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1198 my $host_name = $hit->{hostname};
1199 my $host_key = $hit->{hostkey};
1200 $answer =~ s/KNOWN_SERVER/$host_name/g;
1201 my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1202 &update_jobdb_status_for_send_msgs($answer, $error);
1203 }
1204 }
1206 # target of msg is GOsa
1207 elsif( $answer_target eq "GOSA" ) {
1208 my $session_id = ($1) if $answer =~ /<session_id>(\d+?)<\/session_id>/;
1209 my $add_on = "";
1210 if( defined $session_id ) {
1211 $add_on = ".session_id=$session_id";
1212 }
1213 # answer is for GOSA and has to returned to connected client
1214 my $gosa_answer = &encrypt_msg($answer, $GosaPackages_key);
1215 $client_answer = $gosa_answer.$add_on;
1216 }
1218 # target of msg is job queue at this host
1219 elsif( $answer_target eq "JOBDB") {
1220 $answer =~ /<header>(\S+)<\/header>/;
1221 my $header;
1222 if( defined $1 ) { $header = $1; }
1223 my $error = &send_msg_to_target($answer, $server_address, $GosaPackages_key, $header, $session_id);
1224 &update_jobdb_status_for_send_msgs($answer, $error);
1225 }
1227 # target of msg is a mac address
1228 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 ) {
1229 daemon_log("$session_id INFO: target is mac address '$answer_target', looking for host in known_clients", 5);
1230 my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$answer_target'";
1231 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1232 my $found_ip_flag = 0;
1233 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1234 my $host_name = $hit->{hostname};
1235 my $host_key = $hit->{hostkey};
1236 $answer =~ s/$answer_target/$host_name/g;
1237 daemon_log("$session_id INFO: found host '$host_name', associated to '$answer_target'", 5);
1238 my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1239 &update_jobdb_status_for_send_msgs($answer, $error);
1240 $found_ip_flag++ ;
1241 }
1242 if( $found_ip_flag == 0) {
1243 daemon_log("$session_id WARNING: no host found in known_clients with mac address '$answer_target'", 3);
1244 if( $bus_activ eq "true" ) {
1245 daemon_log("$session_id INFO: try to forward msg '$answer_header' to bus '$bus_address'", 5);
1246 my $sql_statement = "SELECT * FROM known_server WHERE hostname='$bus_address'";
1247 my $query_res = $known_server_db->select_dbentry( $sql_statement );
1248 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1249 my $bus_address = $hit->{hostname};
1250 my $bus_key = $hit->{hostkey};
1251 my $error = &send_msg_to_target($answer, $bus_address, $bus_key, $answer_header, $session_id);
1252 &update_jobdb_status_for_send_msgs($answer, $error);
1253 last;
1254 }
1255 }
1257 }
1259 # answer is for one specific host
1260 } else {
1261 # get encrypt_key
1262 my $encrypt_key = &get_encrypt_key($answer_target);
1263 if( not defined $encrypt_key ) {
1264 # unknown target, forward msg to bus
1265 daemon_log("$session_id WARNING: unknown target '$answer_target'", 3);
1266 if( $bus_activ eq "true" ) {
1267 daemon_log("$session_id INFO: try to forward msg '$answer_header' to bus '$bus_address'", 5);
1268 my $sql_statement = "SELECT * FROM known_server WHERE hostname='$bus_address'";
1269 my $query_res = $known_server_db->select_dbentry( $sql_statement );
1270 my $res_length = keys( %{$query_res} );
1271 if( $res_length == 0 ){
1272 daemon_log("$session_id WARNING: send '$answer_header' to '$bus_address' failed, ".
1273 "no bus found in known_server", 3);
1274 }
1275 else {
1276 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1277 my $bus_key = $hit->{hostkey};
1278 my $error = &send_msg_to_target($answer, $bus_address, $bus_key, $answer_header,$session_id );
1279 &update_jobdb_status_for_send_msgs($answer, $error);
1280 }
1281 }
1282 }
1283 next;
1284 }
1285 my $error = &send_msg_to_target($answer, $answer_target, $encrypt_key, $answer_header,$session_id);
1286 &update_jobdb_status_for_send_msgs($answer, $error);
1287 }
1288 }
1289 }
1290 }
1292 my $filter = POE::Filter::Reference->new();
1293 my %result = (
1294 status => "seems ok to me",
1295 answer => $client_answer,
1296 );
1298 my $output = $filter->put( [ \%result ] );
1299 print @$output;
1302 }
1304 sub session_start {
1305 my ($kernel) = $_[KERNEL];
1306 &trigger_db_loop($kernel);
1307 $global_kernel = $kernel;
1308 $kernel->yield('register_at_foreign_servers');
1309 $kernel->yield('create_fai_server_db', $fai_server_tn );
1310 $kernel->yield('create_fai_release_db', $fai_release_tn );
1311 $kernel->yield('watch_for_next_tasks');
1312 $kernel->sig(USR1 => "sig_handler");
1313 $kernel->sig(USR2 => "create_packages_list_db");
1314 $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1315 $kernel->delay_set('watch_for_done_jobs', $job_queue_loop_delay);
1316 $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay);
1317 $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay);
1318 $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay);
1319 $kernel->delay_set('watch_for_old_known_clients', $job_queue_loop_delay);
1321 }
1323 sub trigger_db_loop {
1324 my ($kernel) = @_ ;
1325 # $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1326 # $kernel->delay_set('watch_for_done_jobs', $job_queue_loop_delay);
1327 # $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay);
1328 # $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay);
1329 # $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay);
1330 # $kernel->delay_set('watch_for_old_known_clients', $job_queue_loop_delay);
1331 }
1334 sub watch_for_done_jobs {
1335 my ($kernel,$heap) = @_[KERNEL, HEAP];
1337 my $sql_statement = "SELECT * FROM ".$job_queue_tn." WHERE status='done'";
1338 my $res = $job_db->select_dbentry( $sql_statement );
1340 while( my ($id, $hit) = each %{$res} ) {
1341 my $jobdb_id = $hit->{id};
1342 my $sql_statement = "DELETE FROM $job_queue_tn WHERE id=$jobdb_id";
1343 my $res = $job_db->del_dbentry($sql_statement);
1344 }
1346 $kernel->delay_set('watch_for_done_jobs',$job_queue_loop_delay);
1347 }
1350 sub watch_for_new_jobs {
1351 if($watch_for_new_jobs_in_progress == 0) {
1352 $watch_for_new_jobs_in_progress = 1;
1353 my ($kernel,$heap) = @_[KERNEL, HEAP];
1355 # check gosa job queue for jobs with executable timestamp
1356 my $timestamp = &get_time();
1357 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE status='waiting' AND (CAST (timestamp AS INTEGER)) < $timestamp ORDER BY timestamp";
1358 my $res = $job_db->exec_statement( $sql_statement );
1360 # Merge all new jobs that would do the same actions
1361 my @drops;
1362 my $hits;
1363 foreach my $hit (reverse @{$res} ) {
1364 my $macaddress= lc @{$hit}[8];
1365 my $headertag= @{$hit}[5];
1366 if(
1367 defined($hits->{$macaddress}) &&
1368 defined($hits->{$macaddress}->{$headertag}) &&
1369 defined($hits->{$macaddress}->{$headertag}[0])
1370 ) {
1371 push @drops, "DELETE FROM $job_queue_tn WHERE id = $hits->{$macaddress}->{$headertag}[0]";
1372 }
1373 $hits->{$macaddress}->{$headertag}= $hit;
1374 }
1376 # Delete new jobs with a matching job in state 'processing'
1377 foreach my $macaddress (keys %{$hits}) {
1378 foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1379 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1380 if(defined($jobdb_id)) {
1381 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND headertag='$jobdb_headertag' AND status='processing'";
1382 my $res = $job_db->exec_statement( $sql_statement );
1383 foreach my $hit (@{$res}) {
1384 push @drops, "DELETE FROM $job_queue_tn WHERE id=$jobdb_id";
1385 }
1386 } else {
1387 daemon_log("J ERROR: Job without id exists for macaddress $macaddress!", 1);
1388 }
1389 }
1390 }
1392 # Commit deletion
1393 $job_db->exec_statementlist(\@drops);
1395 # Look for new jobs that could be executed
1396 foreach my $macaddress (keys %{$hits}) {
1398 # Look if there is an executing job
1399 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND status='processing'";
1400 my $res = $job_db->exec_statement( $sql_statement );
1402 # Skip new jobs for host if there is a processing job
1403 if(defined($res) and defined @{$res}[0]) {
1404 next;
1405 }
1407 foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1408 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1409 if(defined($jobdb_id)) {
1410 my $job_msg = @{$hits->{$macaddress}->{$jobdb_headertag}}[7];
1412 daemon_log("J DEBUG: its time to execute $job_msg", 7);
1413 my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$macaddress'";
1414 my $res_hash = $known_clients_db->select_dbentry( $sql_statement );
1416 # expect macaddress is unique!!!!!!
1417 my $target = $res_hash->{1}->{hostname};
1419 # change header
1420 $job_msg =~ s/<header>job_/<header>gosa_/;
1422 # add sqlite_id
1423 $job_msg =~ s/<\/xml>$/<jobdb_id>$jobdb_id<\/jobdb_id><\/xml>/;
1425 $job_msg =~ /<header>(\S+)<\/header>/;
1426 my $header = $1 ;
1427 my $func_error = &send_msg_to_target($job_msg, $server_address, $GosaPackages_key, $header, "J");
1429 # update status in job queue to 'processing'
1430 $sql_statement = "UPDATE $job_queue_tn SET status='processing' WHERE id=$jobdb_id";
1431 my $res = $job_db->update_dbentry($sql_statement);
1432 # TODO: abfangen ob alles in ordnung ist oder nicht, wenn nicht error schmeißen
1434 # We don't want parallel processing
1435 last;
1436 }
1437 }
1438 }
1440 $watch_for_new_jobs_in_progress = 0;
1441 $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1442 }
1443 }
1446 sub watch_for_new_messages {
1447 my ($kernel,$heap) = @_[KERNEL, HEAP];
1448 my @coll_user_msg; # collection list of outgoing messages
1450 # check messaging_db for new incoming messages with executable timestamp
1451 my $timestamp = &get_time();
1452 my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( (CAST(timestamp AS INTEGER))<$timestamp AND flag='n' AND direction='in' )";
1453 my $res = $messaging_db->exec_statement( $sql_statement );
1454 foreach my $hit (@{$res}) {
1456 # create outgoing messages
1457 my $message_to = @{$hit}[3];
1458 # translate message_to to plain login name
1459 my @message_to_l = split(/,/, $message_to);
1460 my %receiver_h;
1461 foreach my $receiver (@message_to_l) {
1462 if ($receiver =~ /^u_([\s\S]*)$/) {
1463 $receiver_h{$1} = 0;
1464 } elsif ($receiver =~ /^g_([\s\S]*)$/) {
1465 my $group_name = $1;
1466 # fetch all group members from ldap and add them to receiver hash
1467 my $ldap_handle = &get_ldap_handle();
1468 if (defined $ldap_handle) {
1469 my $mesg = $ldap_handle->search(
1470 base => $ldap_base,
1471 scope => 'sub',
1472 attrs => ['memberUid'],
1473 filter => "cn=$group_name",
1474 );
1475 if ($mesg->count) {
1476 my @entries = $mesg->entries;
1477 foreach my $entry (@entries) {
1478 my @receivers= $entry->get_value("memberUid");
1479 foreach my $receiver (@receivers) {
1480 $receiver_h{$1} = 0;
1481 }
1482 }
1483 }
1484 # translating errors ?
1485 if ($mesg->code) {
1486 daemon_log("M ERROR: unable to translate group '$group_name' to user list for message delivery: $mesg->error", 1);
1487 }
1488 # ldap handle error ?
1489 } else {
1490 daemon_log("M ERROR: unable to translate group '$group_name' to user list for message delivery: no ldap handle available", 1);
1491 }
1492 } else {
1493 my $sbjct = &encode_base64(@{$hit}[1]);
1494 my $msg = &encode_base64(@{$hit}[7]);
1495 &daemon_log("M WARNING: unknown receiver '$receiver' for a user-message '$sbjct - $msg'", 3);
1496 }
1497 }
1498 my @receiver_l = keys(%receiver_h);
1500 my $message_id = @{$hit}[0];
1502 #add each outgoing msg to messaging_db
1503 my $receiver;
1504 foreach $receiver (@receiver_l) {
1505 my $sql_statement = "INSERT INTO $messaging_tn (id, subject, message_from, message_to, flag, direction, delivery_time, message, timestamp) ".
1506 "VALUES ('".
1507 $message_id."', '". # id
1508 @{$hit}[1]."', '". # subject
1509 @{$hit}[2]."', '". # message_from
1510 $receiver."', '". # message_to
1511 "none"."', '". # flag
1512 "out"."', '". # direction
1513 @{$hit}[6]."', '". # delivery_time
1514 @{$hit}[7]."', '". # message
1515 $timestamp."'". # timestamp
1516 ")";
1517 &daemon_log("M DEBUG: $sql_statement", 1);
1518 my $res = $messaging_db->exec_statement($sql_statement);
1519 &daemon_log("M INFO: message '".@{$hit}[0]."' is prepared for delivery to receiver '$receiver'", 5);
1520 }
1522 # set incoming message to flag d=deliverd
1523 $sql_statement = "UPDATE $messaging_tn SET flag='p' WHERE id='$message_id'";
1524 &daemon_log("M DEBUG: $sql_statement", 7);
1525 $res = $messaging_db->update_dbentry($sql_statement);
1526 &daemon_log("M INFO: message '$message_id' is set to flag 'p' (processed)", 5);
1527 }
1529 $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay);
1530 return;
1531 }
1533 sub watch_for_delivery_messages {
1534 my ($kernel, $heap) = @_[KERNEL, HEAP];
1536 # select outgoing messages
1537 my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( flag='p' AND direction='out' )";
1538 #&daemon_log("0 DEBUG: $sql", 7);
1539 my $res = $messaging_db->exec_statement( $sql_statement );
1541 # build out msg for each usr
1542 foreach my $hit (@{$res}) {
1543 my $receiver = @{$hit}[3];
1544 my $msg_id = @{$hit}[0];
1545 my $subject = @{$hit}[1];
1546 my $message = @{$hit}[7];
1548 # resolve usr -> host where usr is logged in
1549 my $sql = "SELECT * FROM $login_users_tn WHERE (user='$receiver')";
1550 #&daemon_log("0 DEBUG: $sql", 7);
1551 my $res = $login_users_db->exec_statement($sql);
1553 # reciver is logged in nowhere
1554 if (not ref(@$res[0]) eq "ARRAY") { next; }
1556 my $send_succeed = 0;
1557 foreach my $hit (@$res) {
1558 my $receiver_host = @$hit[0];
1559 &daemon_log("M DEBUG: user '$receiver' is logged in at host '$receiver_host'", 7);
1561 # fetch key to encrypt msg propperly for usr/host
1562 my $sql = "SELECT * FROM $known_clients_tn WHERE (hostname='$receiver_host')";
1563 &daemon_log("0 DEBUG: $sql", 7);
1564 my $res = $known_clients_db->select_dbentry($sql);
1566 # host is already down
1567 if (not ref(@$res[0]) eq "ARRAY") { next; }
1569 # host is on
1570 my $receiver_key = @{@{$res}[0]}[2];
1571 my %data = ('subject' => $subject, 'message' => $message, 'usr' => $receiver);
1572 my $out_msg = &build_msg("usr_msg", $server_address, $receiver_host, \%data );
1573 my $error = &send_msg_to_target($out_msg, $receiver_host, $receiver_key, "usr_msg", 0);
1574 if ($error == 0 ) {
1575 $send_succeed++ ;
1576 }
1577 }
1579 if ($send_succeed) {
1580 # set outgoing msg at db to deliverd
1581 my $sql = "UPDATE $messaging_tn SET flag='d' WHERE (id='$msg_id' AND direction='out' AND message_to='$receiver')";
1582 &daemon_log("0 DEBUG: $sql", 7);
1583 my $res = $messaging_db->exec_statement($sql);
1584 }
1585 }
1587 $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay);
1588 return;
1589 }
1592 sub watch_for_done_messages {
1593 my ($kernel,$heap) = @_[KERNEL, HEAP];
1595 my $sql = "SELECT * FROM $messaging_tn WHERE (flag='p' AND direction='in')";
1596 #&daemon_log("0 DEBUG: $sql", 7);
1597 my $res = $messaging_db->exec_statement($sql);
1599 foreach my $hit (@{$res}) {
1600 my $msg_id = @{$hit}[0];
1602 my $sql = "SELECT * FROM $messaging_tn WHERE (id='$msg_id' AND direction='out' AND (NOT flag='s'))";
1603 #&daemon_log("0 DEBUG: $sql", 7);
1604 my $res = $messaging_db->exec_statement($sql);
1606 # not all usr msgs have been seen till now
1607 if ( ref(@$res[0]) eq "ARRAY") { next; }
1609 $sql = "DELETE FROM $messaging_tn WHERE (id='$msg_id')";
1610 #&daemon_log("0 DEBUG: $sql", 7);
1611 $res = $messaging_db->exec_statement($sql);
1613 }
1615 $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay);
1616 return;
1617 }
1620 sub watch_for_old_known_clients {
1621 my ($kernel,$heap) = @_[KERNEL, HEAP];
1623 my $sql_statement = "SELECT * FROM $known_clients_tn";
1624 my $res = $known_clients_db->select_dbentry( $sql_statement );
1626 my $act_time = int(&get_time());
1628 while ( my ($hit_num, $hit) = each %$res) {
1629 my $expired_timestamp = int($hit->{'timestamp'});
1630 $expired_timestamp =~ /(\d{4})(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)/;
1631 my $dt = DateTime->new( year => $1,
1632 month => $2,
1633 day => $3,
1634 hour => $4,
1635 minute => $5,
1636 second => $6,
1637 );
1639 $dt->add( seconds => 2 * int($hit->{'keylifetime'}) );
1640 $expired_timestamp = $dt->ymd('').$dt->hms('')."\n";
1641 if ($act_time > $expired_timestamp) {
1642 my $hostname = $hit->{'hostname'};
1643 my $del_sql = "DELETE FROM $known_clients_tn WHERE hostname='$hostname'";
1644 my $del_res = $known_clients_db->exec_statement($del_sql);
1646 &main::daemon_log("0 INFO: timestamp '".$hit->{'timestamp'}."' of client '$hostname' is expired('$expired_timestamp'), client will be deleted from known_clients_db", 5);
1647 }
1649 }
1651 $kernel->delay_set('watch_for_old_known_clients', $job_queue_loop_delay);
1652 }
1655 sub watch_for_next_tasks {
1656 my ($kernel,$heap) = @_[KERNEL, HEAP];
1658 my $sql = "SELECT * FROM $incoming_tn";
1659 my $res = $incoming_db->select_dbentry($sql);
1661 while ( my ($hit_num, $hit) = each %$res) {
1662 my $headertag = $hit->{'headertag'};
1663 if ($headertag =~ /^answer_(\d+)/) {
1664 # do not start processing, this message is for a still running POE::Wheel
1665 next;
1666 }
1667 my $message_id = $hit->{'id'};
1668 $kernel->yield('next_task', $hit);
1670 my $sql = "DELETE FROM $incoming_tn WHERE id=$message_id";
1671 my $res = $incoming_db->exec_statement($sql);
1672 }
1674 $kernel->delay_set('watch_for_next_tasks', 1);
1675 }
1678 sub get_ldap_handle {
1679 my ($session_id) = @_;
1680 my $heap;
1681 my $ldap_handle;
1683 if (not defined $session_id ) { $session_id = 0 };
1684 if ($session_id =~ /[^0-9]*/) { $session_id = 0 };
1686 if ($session_id == 0) {
1687 daemon_log("$session_id DEBUG: get_ldap_handle invoked without a session_id, create a new ldap_handle", 7);
1688 $ldap_handle = Net::LDAP->new( $ldap_uri );
1689 $ldap_handle->bind($ldap_admin_dn, apassword => $ldap_admin_password);
1691 } else {
1692 my $session_reference = $global_kernel->ID_id_to_session($session_id);
1693 if( defined $session_reference ) {
1694 $heap = $session_reference->get_heap();
1695 }
1697 if (not defined $heap) {
1698 daemon_log("$session_id DEBUG: cannot get heap for session_id '$session_id'", 7);
1699 return;
1700 }
1702 # TODO: This "if" is nonsense, because it doesn't prove that the
1703 # used handle is still valid - or if we've to reconnect...
1704 #if (not exists $heap->{ldap_handle}) {
1705 $ldap_handle = Net::LDAP->new( $ldap_uri );
1706 $ldap_handle->bind($ldap_admin_dn, password => $ldap_admin_password);
1707 $heap->{ldap_handle} = $ldap_handle;
1708 #}
1709 }
1710 return $ldap_handle;
1711 }
1714 sub change_fai_state {
1715 my ($st, $targets, $session_id) = @_;
1716 $session_id = 0 if not defined $session_id;
1717 # Set FAI state to localboot
1718 my %mapActions= (
1719 reboot => '',
1720 update => 'softupdate',
1721 localboot => 'localboot',
1722 reinstall => 'install',
1723 rescan => '',
1724 wake => '',
1725 memcheck => 'memcheck',
1726 sysinfo => 'sysinfo',
1727 install => 'install',
1728 );
1730 # Return if this is unknown
1731 if (!exists $mapActions{ $st }){
1732 daemon_log("$session_id ERROR: unknown action '$st', can not translate ot FAIstate", 1);
1733 return;
1734 }
1736 my $state= $mapActions{ $st };
1738 my $ldap_handle = &get_ldap_handle($session_id);
1739 if( defined($ldap_handle) ) {
1741 # Build search filter for hosts
1742 my $search= "(&(objectClass=GOhard)";
1743 foreach (@{$targets}){
1744 $search.= "(macAddress=$_)";
1745 }
1746 $search.= ")";
1748 # If there's any host inside of the search string, procress them
1749 if (!($search =~ /macAddress/)){
1750 daemon_log("$session_id ERROR: no macAddress found in filter statement for LDAP search: '$search'", 1);
1751 return;
1752 }
1754 # Perform search for Unit Tag
1755 my $mesg = $ldap_handle->search(
1756 base => $ldap_base,
1757 scope => 'sub',
1758 attrs => ['dn', 'FAIstate', 'objectClass'],
1759 filter => "$search"
1760 );
1762 if ($mesg->count) {
1763 my @entries = $mesg->entries;
1764 foreach my $entry (@entries) {
1765 # Only modify entry if it is not set to '$state'
1766 if ($entry->get_value("FAIstate") ne "$state"){
1767 daemon_log("$session_id INFO: Setting FAIstate to '$state' for ".$entry->dn, 5);
1768 my $result;
1769 my %tmp = map { $_ => 1 } $entry->get_value("objectClass");
1770 if (exists $tmp{'FAIobject'}){
1771 if ($state eq ''){
1772 $result= $ldap_handle->modify($entry->dn, changes => [
1773 delete => [ FAIstate => [] ] ]);
1774 } else {
1775 $result= $ldap_handle->modify($entry->dn, changes => [
1776 replace => [ FAIstate => $state ] ]);
1777 }
1778 } elsif ($state ne ''){
1779 $result= $ldap_handle->modify($entry->dn, changes => [
1780 add => [ objectClass => 'FAIobject' ],
1781 add => [ FAIstate => $state ] ]);
1782 }
1784 # Errors?
1785 if ($result->code){
1786 daemon_log("$session_id Error: Setting FAIstate to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
1787 }
1788 } else {
1789 daemon_log("$session_id DEBUG FAIstate at host '".$entry->dn."' already at state '$st'", 7);
1790 }
1791 }
1792 }
1793 # if no ldap handle defined
1794 } else {
1795 daemon_log("$session_id ERROR: no LDAP handle defined for update FAIstate", 1);
1796 }
1798 }
1801 sub change_goto_state {
1802 my ($st, $targets, $session_id) = @_;
1803 $session_id = 0 if not defined $session_id;
1805 # Switch on or off?
1806 my $state= $st eq 'active' ? 'active': 'locked';
1808 my $ldap_handle = &get_ldap_handle($session_id);
1809 if( defined($ldap_handle) ) {
1811 # Build search filter for hosts
1812 my $search= "(&(objectClass=GOhard)";
1813 foreach (@{$targets}){
1814 $search.= "(macAddress=$_)";
1815 }
1816 $search.= ")";
1818 # If there's any host inside of the search string, procress them
1819 if (!($search =~ /macAddress/)){
1820 return;
1821 }
1823 # Perform search for Unit Tag
1824 my $mesg = $ldap_handle->search(
1825 base => $ldap_base,
1826 scope => 'sub',
1827 attrs => ['dn', 'gotoMode'],
1828 filter => "$search"
1829 );
1831 if ($mesg->count) {
1832 my @entries = $mesg->entries;
1833 foreach my $entry (@entries) {
1835 # Only modify entry if it is not set to '$state'
1836 if ($entry->get_value("gotoMode") ne $state){
1838 daemon_log("$session_id INFO: Setting gotoMode to '$state' for ".$entry->dn, 5);
1839 my $result;
1840 $result= $ldap_handle->modify($entry->dn, changes => [
1841 replace => [ gotoMode => $state ] ]);
1843 # Errors?
1844 if ($result->code){
1845 &daemon_log("$session_id Error: Setting gotoMode to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
1846 }
1848 }
1849 }
1850 }
1852 }
1853 }
1856 sub run_create_fai_server_db {
1857 my ($kernel, $session, $heap, $table_name) = @_[KERNEL, SESSION, HEAP, ARG0];
1858 my $session_id = $session->ID;
1859 my $task = POE::Wheel::Run->new(
1860 Program => sub { &create_fai_server_db($table_name,$kernel, undef, $session_id) },
1861 StdoutEvent => "session_run_result",
1862 StderrEvent => "session_run_debug",
1863 CloseEvent => "session_run_done",
1864 );
1866 $heap->{task}->{ $task->ID } = $task;
1867 return;
1868 }
1871 sub create_fai_server_db {
1872 my ($table_name, $kernel, $dont_create_packages_list, $session_id) = @_;
1873 my $result;
1875 if (not defined $session_id) { $session_id = 0; }
1876 my $ldap_handle = &get_ldap_handle();
1877 if(defined($ldap_handle)) {
1878 daemon_log("$session_id INFO: create_fai_server_db: start", 5);
1879 my $mesg= $ldap_handle->search(
1880 base => $ldap_base,
1881 scope => 'sub',
1882 attrs => ['FAIrepository', 'gosaUnitTag'],
1883 filter => "(&(FAIrepository=*)(objectClass=FAIrepositoryServer))",
1884 );
1885 if($mesg->{'resultCode'} == 0 &&
1886 $mesg->count != 0) {
1887 foreach my $entry (@{$mesg->{entries}}) {
1888 if($entry->exists('FAIrepository')) {
1889 # Add an entry for each Repository configured for server
1890 foreach my $repo(@{$entry->get_value('FAIrepository', asref => 1)}) {
1891 my($tmp_url,$tmp_server,$tmp_release,$tmp_sections) = split(/\|/, $repo);
1892 my $tmp_tag= $entry->get_value('gosaUnitTag') || "";
1893 $result= $fai_server_db->add_dbentry( {
1894 table => $table_name,
1895 primkey => ['server', 'release', 'tag'],
1896 server => $tmp_url,
1897 release => $tmp_release,
1898 sections => $tmp_sections,
1899 tag => (length($tmp_tag)>0)?$tmp_tag:"",
1900 } );
1901 }
1902 }
1903 }
1904 }
1905 daemon_log("$session_id INFO: create_fai_server_db: finished", 5);
1907 # TODO: Find a way to post the 'create_packages_list_db' event
1908 if(not defined($dont_create_packages_list)) {
1909 &create_packages_list_db(undef, undef, $session_id);
1910 }
1911 }
1913 $ldap_handle->disconnect;
1914 return $result;
1915 }
1918 sub run_create_fai_release_db {
1919 my ($session, $heap, $table_name) = @_[SESSION, HEAP, ARG0];
1920 my $session_id = $session->ID;
1921 my $task = POE::Wheel::Run->new(
1922 Program => sub { &create_fai_release_db($table_name, $session_id) },
1923 StdoutEvent => "session_run_result",
1924 StderrEvent => "session_run_debug",
1925 CloseEvent => "session_run_done",
1926 );
1928 $heap->{task}->{ $task->ID } = $task;
1929 return;
1930 }
1933 sub create_fai_release_db {
1934 my ($table_name, $session_id) = @_;
1935 my $result;
1937 # used for logging
1938 if (not defined $session_id) { $session_id = 0; }
1940 my $ldap_handle = &get_ldap_handle();
1941 if(defined($ldap_handle)) {
1942 daemon_log("$session_id INFO: create_fai_release_db: start",5);
1943 my $mesg= $ldap_handle->search(
1944 base => $ldap_base,
1945 scope => 'sub',
1946 attrs => [],
1947 filter => "(&(objectClass=organizationalUnit)(ou=fai))",
1948 );
1949 if($mesg->{'resultCode'} == 0 &&
1950 $mesg->count != 0) {
1951 # Walk through all possible FAI container ou's
1952 my @sql_list;
1953 my $timestamp= &get_time();
1954 foreach my $ou (@{$mesg->{entries}}) {
1955 my $tmp_classes= resolve_fai_classes($ou->dn, $ldap_handle, $session_id);
1956 if(defined($tmp_classes) && ref($tmp_classes) eq 'HASH') {
1957 my @tmp_array=get_fai_release_entries($tmp_classes);
1958 if(@tmp_array) {
1959 foreach my $entry (@tmp_array) {
1960 if(defined($entry) && ref($entry) eq 'HASH') {
1961 my $sql=
1962 "INSERT INTO $table_name "
1963 ."(timestamp, release, class, type, state) VALUES ("
1964 .$timestamp.","
1965 ."'".$entry->{'release'}."',"
1966 ."'".$entry->{'class'}."',"
1967 ."'".$entry->{'type'}."',"
1968 ."'".$entry->{'state'}."')";
1969 push @sql_list, $sql;
1970 }
1971 }
1972 }
1973 }
1974 }
1976 daemon_log("$session_id DEBUG: Inserting ".scalar @sql_list." entries to DB",8);
1977 if(@sql_list) {
1978 unshift @sql_list, "VACUUM";
1979 unshift @sql_list, "DELETE FROM $table_name";
1980 $fai_release_db->exec_statementlist(\@sql_list);
1981 }
1982 daemon_log("$session_id DEBUG: Done with inserting",7);
1983 }
1984 daemon_log("$session_id INFO: create_fai_release_db: finished",5);
1985 }
1986 $ldap_handle->disconnect;
1987 return $result;
1988 }
1990 sub get_fai_types {
1991 my $tmp_classes = shift || return undef;
1992 my @result;
1994 foreach my $type(keys %{$tmp_classes}) {
1995 if(defined($tmp_classes->{$type}[0]) && (!($tmp_classes->{$type}[0] =~ /^.*?removed.*?$/))) {
1996 my $entry = {
1997 type => $type,
1998 state => $tmp_classes->{$type}[0],
1999 };
2000 push @result, $entry;
2001 }
2002 }
2004 return @result;
2005 }
2007 sub get_fai_state {
2008 my $result = "";
2009 my $tmp_classes = shift || return $result;
2011 foreach my $type(keys %{$tmp_classes}) {
2012 if(defined($tmp_classes->{$type}[0])) {
2013 $result = $tmp_classes->{$type}[0];
2015 # State is equal for all types in class
2016 last;
2017 }
2018 }
2020 return $result;
2021 }
2023 sub resolve_fai_classes {
2024 my ($fai_base, $ldap_handle, $session_id) = @_;
2025 if (not defined $session_id) { $session_id = 0; }
2026 my $result;
2027 my @possible_fai_classes= ("FAIscript", "FAIhook", "FAIpartitionTable", "FAItemplate", "FAIvariable", "FAIprofile", "FAIpackageList");
2028 my $fai_filter= "(|(&(objectClass=FAIclass)(|(objectClass=".join(")(objectClass=", @possible_fai_classes).")))(objectClass=FAIbranch))";
2029 my $fai_classes;
2031 daemon_log("$session_id DEBUG: Searching for FAI entries in base $fai_base",7);
2032 my $mesg= $ldap_handle->search(
2033 base => $fai_base,
2034 scope => 'sub',
2035 attrs => ['cn','objectClass','FAIstate'],
2036 filter => $fai_filter,
2037 );
2038 daemon_log("$session_id DEBUG: Found ".$mesg->count()." FAI entries",7);
2040 if($mesg->{'resultCode'} == 0 &&
2041 $mesg->count != 0) {
2042 foreach my $entry (@{$mesg->{entries}}) {
2043 if($entry->exists('cn')) {
2044 my $tmp_dn= $entry->dn();
2046 # Skip classname and ou dn parts for class
2047 my $tmp_release = ($1) if $tmp_dn =~ /^[^,]+,[^,]+,(.*?),$fai_base$/;
2049 # Skip classes without releases
2050 if((!defined($tmp_release)) || length($tmp_release)==0) {
2051 next;
2052 }
2054 my $tmp_cn= $entry->get_value('cn');
2055 my $tmp_state= $entry->get_value('FAIstate');
2057 my $tmp_type;
2058 # Get FAI type
2059 for my $oclass(@{$entry->get_value('objectClass', asref => 1)}) {
2060 if(grep $_ eq $oclass, @possible_fai_classes) {
2061 $tmp_type= $oclass;
2062 last;
2063 }
2064 }
2066 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
2067 # A Subrelease
2068 my @sub_releases = split(/,/, $tmp_release);
2070 # Walk through subreleases and build hash tree
2071 my $hash;
2072 while(my $tmp_sub_release = pop @sub_releases) {
2073 $hash .= "\{'$tmp_sub_release'\}->";
2074 }
2075 eval('push @{$fai_classes->'.$hash.'{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";');
2076 } else {
2077 # A branch, no subrelease
2078 push @{$fai_classes->{$tmp_release}->{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";
2079 }
2080 } elsif (!$entry->exists('cn')) {
2081 my $tmp_dn= $entry->dn();
2082 my $tmp_release = ($1) if $tmp_dn =~ /^(.*?),$fai_base$/;
2084 # Skip classes without releases
2085 if((!defined($tmp_release)) || length($tmp_release)==0) {
2086 next;
2087 }
2089 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
2090 # A Subrelease
2091 my @sub_releases= split(/,/, $tmp_release);
2093 # Walk through subreleases and build hash tree
2094 my $hash;
2095 while(my $tmp_sub_release = pop @sub_releases) {
2096 $hash .= "\{'$tmp_sub_release'\}->";
2097 }
2098 # Remove the last two characters
2099 chop($hash);
2100 chop($hash);
2102 eval('$fai_classes->'.$hash.'= {}');
2103 } else {
2104 # A branch, no subrelease
2105 if(!exists($fai_classes->{$tmp_release})) {
2106 $fai_classes->{$tmp_release} = {};
2107 }
2108 }
2109 }
2110 }
2112 # The hash is complete, now we can honor the copy-on-write based missing entries
2113 foreach my $release (keys %$fai_classes) {
2114 $result->{$release}= deep_copy(apply_fai_inheritance($fai_classes->{$release}));
2115 }
2116 }
2117 return $result;
2118 }
2120 sub apply_fai_inheritance {
2121 my $fai_classes = shift || return {};
2122 my $tmp_classes;
2124 # Get the classes from the branch
2125 foreach my $class (keys %{$fai_classes}) {
2126 # Skip subreleases
2127 if($class =~ /^ou=.*$/) {
2128 next;
2129 } else {
2130 $tmp_classes->{$class}= deep_copy($fai_classes->{$class});
2131 }
2132 }
2134 # Apply to each subrelease
2135 foreach my $subrelease (keys %{$fai_classes}) {
2136 if($subrelease =~ /ou=/) {
2137 foreach my $tmp_class (keys %{$tmp_classes}) {
2138 if(!exists($fai_classes->{$subrelease}->{$tmp_class})) {
2139 $fai_classes->{$subrelease}->{$tmp_class} =
2140 deep_copy($tmp_classes->{$tmp_class});
2141 } else {
2142 foreach my $type (keys %{$tmp_classes->{$tmp_class}}) {
2143 if(!exists($fai_classes->{$subrelease}->{$tmp_class}->{$type})) {
2144 $fai_classes->{$subrelease}->{$tmp_class}->{$type}=
2145 deep_copy($tmp_classes->{$tmp_class}->{$type});
2146 }
2147 }
2148 }
2149 }
2150 }
2151 }
2153 # Find subreleases in deeper levels
2154 foreach my $subrelease (keys %{$fai_classes}) {
2155 if($subrelease =~ /ou=/) {
2156 foreach my $subsubrelease (keys %{$fai_classes->{$subrelease}}) {
2157 if($subsubrelease =~ /ou=/) {
2158 apply_fai_inheritance($fai_classes->{$subrelease});
2159 }
2160 }
2161 }
2162 }
2164 return $fai_classes;
2165 }
2167 sub get_fai_release_entries {
2168 my $tmp_classes = shift || return;
2169 my $parent = shift || "";
2170 my @result = shift || ();
2172 foreach my $entry (keys %{$tmp_classes}) {
2173 if(defined($entry)) {
2174 if($entry =~ /^ou=.*$/) {
2175 my $release_name = $entry;
2176 $release_name =~ s/ou=//g;
2177 if(length($parent)>0) {
2178 $release_name = $parent."/".$release_name;
2179 }
2180 my @bufentries = get_fai_release_entries($tmp_classes->{$entry}, $release_name, @result);
2181 foreach my $bufentry(@bufentries) {
2182 push @result, $bufentry;
2183 }
2184 } else {
2185 my @types = get_fai_types($tmp_classes->{$entry});
2186 foreach my $type (@types) {
2187 push @result,
2188 {
2189 'class' => $entry,
2190 'type' => $type->{'type'},
2191 'release' => $parent,
2192 'state' => $type->{'state'},
2193 };
2194 }
2195 }
2196 }
2197 }
2199 return @result;
2200 }
2202 sub deep_copy {
2203 my $this = shift;
2204 if (not ref $this) {
2205 $this;
2206 } elsif (ref $this eq "ARRAY") {
2207 [map deep_copy($_), @$this];
2208 } elsif (ref $this eq "HASH") {
2209 +{map { $_ => deep_copy($this->{$_}) } keys %$this};
2210 } else { die "what type is $_?" }
2211 }
2214 sub session_run_result {
2215 my ($kernel, $heap, $client_answer) = @_[KERNEL, HEAP, ARG0];
2216 $kernel->sig(CHLD => "child_reap");
2217 }
2219 sub session_run_debug {
2220 my $result = $_[ARG0];
2221 print STDERR "$result\n";
2222 }
2224 sub session_run_done {
2225 my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
2226 delete $heap->{task}->{$task_id};
2227 }
2230 sub create_sources_list {
2231 my $session_id = shift;
2232 my $ldap_handle = &main::get_ldap_handle;
2233 my $result="/tmp/gosa_si_tmp_sources_list";
2235 # Remove old file
2236 if(stat($result)) {
2237 unlink($result);
2238 &main::daemon_log("$session_id DEBUG: remove an old version of '$result'", 7);
2239 }
2241 my $fh;
2242 open($fh, ">$result");
2243 if (not defined $fh) {
2244 &main::daemon_log("$session_id DEBUG: cannot open '$result' for writing", 7);
2245 return undef;
2246 }
2247 if(defined($main::ldap_server_dn) and length($main::ldap_server_dn) > 0) {
2248 my $mesg=$ldap_handle->search(
2249 base => $main::ldap_server_dn,
2250 scope => 'base',
2251 attrs => 'FAIrepository',
2252 filter => 'objectClass=FAIrepositoryServer'
2253 );
2254 if($mesg->count) {
2255 foreach my $entry(@{$mesg->{'entries'}}) {
2256 foreach my $value(@{$entry->get_value('FAIrepository', asref => 1)}) {
2257 my ($server, $tag, $release, $sections)= split /\|/, $value;
2258 my $line = "deb $server $release";
2259 $sections =~ s/,/ /g;
2260 $line.= " $sections";
2261 print $fh $line."\n";
2262 }
2263 }
2264 }
2265 } else {
2266 if (defined $main::ldap_server_dn){
2267 &main::daemon_log("$session_id ERROR: something wrong with ldap_server_dn '$main::ldap_server_dn', abort create_sources_list", 1);
2268 } else {
2269 &main::daemon_log("$session_id ERROR: no ldap_server_dn found, abort create_sources_list", 1);
2270 }
2271 }
2272 close($fh);
2274 return $result;
2275 }
2278 sub run_create_packages_list_db {
2279 my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
2280 my $session_id = $session->ID;
2282 my $task = POE::Wheel::Run->new(
2283 Priority => +20,
2284 Program => sub {&create_packages_list_db(undef, undef, $session_id)},
2285 StdoutEvent => "session_run_result",
2286 StderrEvent => "session_run_debug",
2287 CloseEvent => "session_run_done",
2288 );
2289 $heap->{task}->{ $task->ID } = $task;
2290 }
2293 sub create_packages_list_db {
2294 my ($ldap_handle, $sources_file, $session_id) = @_;
2296 # it should not be possible to trigger a recreation of packages_list_db
2297 # while packages_list_db is under construction, so set flag packages_list_under_construction
2298 # which is tested befor recreation can be started
2299 if (-r $packages_list_under_construction) {
2300 daemon_log("$session_id WARNING: packages_list_db is right now under construction, please wait until this process is finished", 3);
2301 return;
2302 } else {
2303 daemon_log("$session_id INFO: create_packages_list_db: start", 5);
2304 # set packages_list_under_construction to true
2305 system("touch $packages_list_under_construction");
2306 @packages_list_statements=();
2307 }
2309 if (not defined $session_id) { $session_id = 0; }
2310 if (not defined $ldap_handle) {
2311 $ldap_handle= &get_ldap_handle();
2313 if (not defined $ldap_handle) {
2314 daemon_log("$session_id ERROR: no ldap_handle available to create_packages_list_db", 1);
2315 unlink($packages_list_under_construction);
2316 return;
2317 }
2318 }
2319 if (not defined $sources_file) {
2320 &main::daemon_log("$session_id INFO: no sources_file given for creating packages list so trigger creation of it", 5);
2321 $sources_file = &create_sources_list($session_id);
2322 }
2324 if (not defined $sources_file) {
2325 &main::daemon_log("$session_id ERROR: no sources_file given under '$sources_file', skip create_packages_list_db", 1);
2326 unlink($packages_list_under_construction);
2327 return;
2328 }
2330 my $line;
2332 open(CONFIG, "<$sources_file") or do {
2333 daemon_log( "$session_id ERROR: create_packages_list_db: Failed to open '$sources_file'", 1);
2334 unlink($packages_list_under_construction);
2335 return;
2336 };
2338 # Read lines
2339 while ($line = <CONFIG>){
2340 # Unify
2341 chop($line);
2342 $line =~ s/^\s+//;
2343 $line =~ s/^\s+/ /;
2345 # Strip comments
2346 $line =~ s/#.*$//g;
2348 # Skip empty lines
2349 if ($line =~ /^\s*$/){
2350 next;
2351 }
2353 # Interpret deb line
2354 if ($line =~ /^deb [^\s]+\s[^\s]+\s[^\s]+/){
2355 my( $baseurl, $dist, $sections ) = ($line =~ /^deb\s([^\s]+)\s+([^\s]+)\s+(.*)$/);
2356 my $section;
2357 foreach $section (split(' ', $sections)){
2358 &parse_package_info( $baseurl, $dist, $section, $session_id );
2359 }
2360 }
2361 }
2363 close (CONFIG);
2365 find(\&cleanup_and_extract, keys( %repo_dirs ));
2366 &main::strip_packages_list_statements();
2367 unshift @packages_list_statements, "VACUUM";
2368 $packages_list_db->exec_statementlist(\@packages_list_statements);
2369 unlink($packages_list_under_construction);
2370 daemon_log("$session_id INFO: create_packages_list_db: finished", 5);
2371 return;
2372 }
2374 # This function should do some intensive task to minimize the db-traffic
2375 sub strip_packages_list_statements {
2376 my @existing_entries= @{$packages_list_db->exec_statement("SELECT * FROM $main::packages_list_tn")};
2377 my @new_statement_list=();
2378 my $hash;
2379 my $insert_hash;
2380 my $update_hash;
2381 my $delete_hash;
2382 my $local_timestamp=get_time();
2384 foreach my $existing_entry (@existing_entries) {
2385 $hash->{@{$existing_entry}[0]}->{@{$existing_entry}[1]}->{@{$existing_entry}[2]}= $existing_entry;
2386 }
2388 foreach my $statement (@packages_list_statements) {
2389 if($statement =~ /^INSERT/i) {
2390 # Assign the values from the insert statement
2391 my ($distribution,$package,$version,$section,$description,$template,$timestamp) = ($1,$2,$3,$4,$5,$6,$7) if $statement =~
2392 /^INSERT\s+?INTO\s+?$main::packages_list_tn\s+?VALUES\s*?\('(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)'\s*?\)$/si;
2393 if(exists($hash->{$distribution}->{$package}->{$version})) {
2394 # If section or description has changed, update the DB
2395 if(
2396 (! (@{$hash->{$distribution}->{$package}->{$version}}[3] eq $section)) or
2397 (! (@{$hash->{$distribution}->{$package}->{$version}}[4] eq $description))
2398 ) {
2399 @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,undef);
2400 }
2401 } else {
2402 # Insert a non-existing entry to db
2403 @{$insert_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
2404 }
2405 } elsif ($statement =~ /^UPDATE/i) {
2406 my ($template,$package,$version) = ($1,$2,$3) if $statement =~
2407 /^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;
2408 foreach my $distribution (keys %{$hash}) {
2409 if(exists($insert_hash->{$distribution}->{$package}->{$version})) {
2410 # update the insertion hash to execute only one query per package (insert instead insert+update)
2411 @{$insert_hash->{$distribution}->{$package}->{$version}}[5]= $template;
2412 } elsif(exists($hash->{$distribution}->{$package}->{$version})) {
2413 if( ! (@{$hash->{$distribution}->{$package}->{$version}}[5] eq $template)) {
2414 my $section;
2415 my $description;
2416 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) and
2417 length(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) > 0 ) {
2418 $section= @{$update_hash->{$distribution}->{$package}->{$version}}[3];
2419 }
2420 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
2421 $description= @{$update_hash->{$distribution}->{$package}->{$version}}[4];
2422 }
2423 @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
2424 }
2425 }
2426 }
2427 }
2428 }
2430 # TODO: Check for orphaned entries
2432 # unroll the insert_hash
2433 foreach my $distribution (keys %{$insert_hash}) {
2434 foreach my $package (keys %{$insert_hash->{$distribution}}) {
2435 foreach my $version (keys %{$insert_hash->{$distribution}->{$package}}) {
2436 push @new_statement_list, "INSERT INTO $main::packages_list_tn VALUES ('$distribution','$package','$version',"
2437 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[3]',"
2438 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[4]',"
2439 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[5]',"
2440 ."'$local_timestamp')";
2441 }
2442 }
2443 }
2445 # unroll the update hash
2446 foreach my $distribution (keys %{$update_hash}) {
2447 foreach my $package (keys %{$update_hash->{$distribution}}) {
2448 foreach my $version (keys %{$update_hash->{$distribution}->{$package}}) {
2449 my $set = "";
2450 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3])) {
2451 $set .= "section = '@{$update_hash->{$distribution}->{$package}->{$version}}[3]', ";
2452 }
2453 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
2454 $set .= "description = '@{$update_hash->{$distribution}->{$package}->{$version}}[4]', ";
2455 }
2456 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[5])) {
2457 $set .= "template = '@{$update_hash->{$distribution}->{$package}->{$version}}[5]', ";
2458 }
2459 if(defined($set) and length($set) > 0) {
2460 $set .= "timestamp = '$local_timestamp'";
2461 } else {
2462 next;
2463 }
2464 push @new_statement_list,
2465 "UPDATE $main::packages_list_tn SET $set WHERE"
2466 ." distribution = '$distribution'"
2467 ." AND package = '$package'"
2468 ." AND version = '$version'";
2469 }
2470 }
2471 }
2473 @packages_list_statements = @new_statement_list;
2474 }
2477 sub parse_package_info {
2478 my ($baseurl, $dist, $section, $session_id)= @_;
2479 my ($package);
2480 if (not defined $session_id) { $session_id = 0; }
2481 my ($path) = ($baseurl =~ m%://[^/]*(.*)$%);
2482 $repo_dirs{ "${repo_path}/pool" } = 1;
2484 foreach $package ("Packages.gz"){
2485 daemon_log("$session_id DEBUG: create_packages_list: fetch $baseurl, $dist, $section", 7);
2486 get_package( "$baseurl/dists/$dist/$section/binary-$arch/$package", "$outdir/$dist/$section", $session_id );
2487 parse_package( "$outdir/$dist/$section", $dist, $path, $session_id );
2488 }
2490 }
2493 sub get_package {
2494 my ($url, $dest, $session_id)= @_;
2495 if (not defined $session_id) { $session_id = 0; }
2497 my $tpath = dirname($dest);
2498 -d "$tpath" || mkpath "$tpath";
2500 # This is ugly, but I've no time to take a look at "how it works in perl"
2501 if(0 == system("wget '$url' -O '$dest' 2>/dev/null") ) {
2502 system("gunzip -cd '$dest' > '$dest.in'");
2503 daemon_log("$session_id DEBUG: run command: gunzip -cd '$dest' > '$dest.in'", 5);
2504 unlink($dest);
2505 daemon_log("$session_id DEBUG: delete file '$dest'", 5);
2506 } else {
2507 daemon_log("$session_id ERROR: create_packages_list_db: get_packages: fetching '$url' failed!", 1);
2508 }
2509 return 0;
2510 }
2513 sub parse_package {
2514 my ($path, $dist, $srv_path, $session_id)= @_;
2515 if (not defined $session_id) { $session_id = 0;}
2516 my ($package, $version, $section, $description);
2517 my $PACKAGES;
2518 my $timestamp = &get_time();
2520 if(not stat("$path.in")) {
2521 daemon_log("$session_id ERROR: create_packages_list: parse_package: file '$path.in' is not readable",1);
2522 return;
2523 }
2525 open($PACKAGES, "<$path.in");
2526 if(not defined($PACKAGES)) {
2527 daemon_log("$session_id ERROR: create_packages_list_db: parse_package: cannot open '$path.in'",1);
2528 return;
2529 }
2531 # Read lines
2532 while (<$PACKAGES>){
2533 my $line = $_;
2534 # Unify
2535 chop($line);
2537 # Use empty lines as a trigger
2538 if ($line =~ /^\s*$/){
2539 my $sql = "INSERT INTO packages_list VALUES ('$dist', '$package', '$version', '$section', '$description', '', '$timestamp')";
2540 push(@packages_list_statements, $sql);
2541 $package = "none";
2542 $version = "none";
2543 $section = "none";
2544 $description = "none";
2545 next;
2546 }
2548 # Trigger for package name
2549 if ($line =~ /^Package:\s/){
2550 ($package)= ($line =~ /^Package: (.*)$/);
2551 next;
2552 }
2554 # Trigger for version
2555 if ($line =~ /^Version:\s/){
2556 ($version)= ($line =~ /^Version: (.*)$/);
2557 next;
2558 }
2560 # Trigger for description
2561 if ($line =~ /^Description:\s/){
2562 ($description)= &encode_base64(($line =~ /^Description: (.*)$/));
2563 next;
2564 }
2566 # Trigger for section
2567 if ($line =~ /^Section:\s/){
2568 ($section)= ($line =~ /^Section: (.*)$/);
2569 next;
2570 }
2572 # Trigger for filename
2573 if ($line =~ /^Filename:\s/){
2574 my ($filename) = ($line =~ /^Filename: (.*)$/);
2575 store_fileinfo( $package, $filename, $dist, $srv_path, $version, $repo_path );
2576 next;
2577 }
2578 }
2580 close( $PACKAGES );
2581 unlink( "$path.in" );
2582 &main::daemon_log("$session_id DEBUG: unlink '$path.in'", 1);
2583 }
2586 sub store_fileinfo {
2587 my( $package, $file, $dist, $path, $vers, $srvdir) = @_;
2589 my %fileinfo = (
2590 'package' => $package,
2591 'dist' => $dist,
2592 'version' => $vers,
2593 );
2595 $repo_files{ "${srvdir}/$file" } = \%fileinfo;
2596 }
2599 sub cleanup_and_extract {
2600 my $fileinfo = $repo_files{ $File::Find::name };
2602 if( defined $fileinfo ) {
2604 my $dir = "$outdir/$fileinfo->{ 'dist' }/debconf.d";
2605 my $sql;
2606 my $package = $fileinfo->{ 'package' };
2607 my $newver = $fileinfo->{ 'version' };
2609 mkpath($dir);
2610 system( "dpkg -e '$File::Find::name' '$dir/DEBIAN'" );
2612 if( -f "$dir/DEBIAN/templates" ) {
2614 daemon_log("DEBUG: Found debconf templates in '$package' - $newver", 5);
2616 my $tmpl= "";
2617 {
2618 local $/=undef;
2619 open FILE, "$dir/DEBIAN/templates";
2620 $tmpl = &encode_base64(<FILE>);
2621 close FILE;
2622 }
2623 rmtree("$dir/DEBIAN/templates");
2625 $sql= "update $main::packages_list_tn set template = '$tmpl' where package = '$package' and version = '$newver';";
2626 push @packages_list_statements, $sql;
2627 }
2628 }
2630 return;
2631 }
2634 sub register_at_foreign_servers {
2635 my ($kernel) = $_[KERNEL];
2637 # hole alle bekannten server aus known_server_db
2638 my $server_sql = "SELECT * FROM $known_server_tn";
2639 my $server_res = $known_server_db->exec_statement($server_sql);
2641 # no entries in known_server_db
2642 if (not ref(@$server_res[0]) eq "ARRAY") {
2643 # TODO
2644 }
2646 # detect already connected clients
2647 my $client_sql = "SELECT * FROM $known_clients_tn";
2648 my $client_res = $known_clients_db->exec_statement($client_sql);
2650 # send my server details to all other gosa-si-server within the network
2651 foreach my $hit (@$server_res) {
2652 my $hostname = @$hit[0];
2653 my $hostkey = &create_passwd;
2655 # add already connected clients to registration message
2656 my $myhash = &create_xml_hash('new_server', $server_address, $hostname);
2657 &add_content2xml_hash($myhash, 'key', $hostkey);
2658 map(&add_content2xml_hash($myhash, 'client', @{$_}[0].",".@{$_}[4]), @$client_res);
2660 # build registration message and send it
2661 my $foreign_server_msg = &create_xml_string($myhash);
2662 my $error = &send_msg_to_target($foreign_server_msg, $hostname, $ServerPackages_key, "new_server", 0);
2663 }
2665 $kernel->delay_set("register_at_foreign_servers", $foreign_servers_register_delay);
2666 return;
2667 }
2670 #==== MAIN = main ==============================================================
2671 # parse commandline options
2672 Getopt::Long::Configure( "bundling" );
2673 GetOptions("h|help" => \&usage,
2674 "c|config=s" => \$cfg_file,
2675 "f|foreground" => \$foreground,
2676 "v|verbose+" => \$verbose,
2677 "no-bus+" => \$no_bus,
2678 "no-arp+" => \$no_arp,
2679 );
2681 # read and set config parameters
2682 &check_cmdline_param ;
2683 &read_configfile;
2684 &check_pid;
2686 $SIG{CHLD} = 'IGNORE';
2688 # forward error messages to logfile
2689 if( ! $foreground ) {
2690 open( STDIN, '+>/dev/null' );
2691 open( STDOUT, '+>&STDIN' );
2692 open( STDERR, '+>&STDIN' );
2693 }
2695 # Just fork, if we are not in foreground mode
2696 if( ! $foreground ) {
2697 chdir '/' or die "Can't chdir to /: $!";
2698 $pid = fork;
2699 setsid or die "Can't start a new session: $!";
2700 umask 0;
2701 } else {
2702 $pid = $$;
2703 }
2705 # Do something useful - put our PID into the pid_file
2706 if( 0 != $pid ) {
2707 open( LOCK_FILE, ">$pid_file" );
2708 print LOCK_FILE "$pid\n";
2709 close( LOCK_FILE );
2710 if( !$foreground ) {
2711 exit( 0 )
2712 };
2713 }
2715 # parse head url and revision from svn
2716 my $server_status_hash = { 'developmental'=>'revision', 'stable'=>'release'};
2717 $server_version =~ /^\$HeadURL: (\S+) \$:\$Rev: (\d+) \$$/;
2718 $server_headURL = defined $1 ? $1 : 'unknown' ;
2719 $server_revision = defined $2 ? $2 : 'unknown' ;
2720 if ($server_headURL =~ /\/tag\// ||
2721 $server_headURL =~ /\/branches\// ) {
2722 $server_status = "stable";
2723 } else {
2724 $server_status = "developmental" ;
2725 }
2728 daemon_log(" ", 1);
2729 daemon_log("$0 started!", 1);
2730 daemon_log("status: $server_status", 1);
2731 daemon_log($server_status_hash->{$server_status}.": $server_revision", 1);
2733 if ($no_bus > 0) {
2734 $bus_activ = "false"
2735 }
2737 # connect to incoming_db
2738 unlink($incoming_file_name);
2739 $incoming_db = GOSA::DBsqlite->new($incoming_file_name);
2740 $incoming_db->create_table($incoming_tn, \@incoming_col_names);
2742 # connect to gosa-si job queue
2743 $job_db = GOSA::DBsqlite->new($job_queue_file_name);
2744 $job_db->create_table($job_queue_tn, \@job_queue_col_names);
2746 # connect to known_clients_db
2747 $known_clients_db = GOSA::DBsqlite->new($known_clients_file_name);
2748 $known_clients_db->create_table($known_clients_tn, \@known_clients_col_names);
2750 # connect to foreign_clients_db
2751 $foreign_clients_db = GOSA::DBsqlite->new($foreign_clients_file_name);
2752 $foreign_clients_db->create_table($foreign_clients_tn, \@foreign_clients_col_names);
2754 # connect to known_server_db
2755 unlink($known_server_file_name);
2756 $known_server_db = GOSA::DBsqlite->new($known_server_file_name);
2757 $known_server_db->create_table($known_server_tn, \@known_server_col_names);
2759 # connect to login_usr_db
2760 $login_users_db = GOSA::DBsqlite->new($login_users_file_name);
2761 $login_users_db->create_table($login_users_tn, \@login_users_col_names);
2763 # connect to fai_server_db and fai_release_db
2764 unlink($fai_server_file_name);
2765 $fai_server_db = GOSA::DBsqlite->new($fai_server_file_name);
2766 $fai_server_db->create_table($fai_server_tn, \@fai_server_col_names);
2768 unlink($fai_release_file_name);
2769 $fai_release_db = GOSA::DBsqlite->new($fai_release_file_name);
2770 $fai_release_db->create_table($fai_release_tn, \@fai_release_col_names);
2772 # connect to packages_list_db
2773 #unlink($packages_list_file_name);
2774 unlink($packages_list_under_construction);
2775 $packages_list_db = GOSA::DBsqlite->new($packages_list_file_name);
2776 $packages_list_db->create_table($packages_list_tn, \@packages_list_col_names);
2778 # connect to messaging_db
2779 $messaging_db = GOSA::DBsqlite->new($messaging_file_name);
2780 $messaging_db->create_table($messaging_tn, \@messaging_col_names);
2783 # create xml object used for en/decrypting
2784 $xml = new XML::Simple();
2787 # foreign servers
2788 my @foreign_server_list;
2790 # add foreign server from cfg file
2791 if ($foreign_server_string ne "") {
2792 my @cfg_foreign_server_list = split(",", $foreign_server_string);
2793 foreach my $foreign_server (@cfg_foreign_server_list) {
2794 push(@foreign_server_list, $foreign_server);
2795 }
2796 }
2798 # add foreign server from dns
2799 my @tmp_servers;
2800 if ( !$server_domain) {
2801 # Try our DNS Searchlist
2802 for my $domain(get_dns_domains()) {
2803 chomp($domain);
2804 my @tmp_domains= &get_server_addresses($domain);
2805 if(@tmp_domains) {
2806 for my $tmp_server(@tmp_domains) {
2807 push @tmp_servers, $tmp_server;
2808 }
2809 }
2810 }
2811 if(@tmp_servers && length(@tmp_servers)==0) {
2812 daemon_log("0 WARNING: no foreign gosa-si-server found in DNS for domain '$server_domain'", 3);
2813 }
2814 } else {
2815 @tmp_servers = &get_server_addresses($server_domain);
2816 if( 0 == @tmp_servers ) {
2817 daemon_log("0 WARNING: no foreign gosa-si-server found in DNS for domain '$server_domain'", 3);
2818 }
2819 }
2820 foreach my $server (@tmp_servers) {
2821 unshift(@foreign_server_list, $server);
2822 }
2823 # eliminate duplicate entries
2824 @foreign_server_list = &del_doubles(@foreign_server_list);
2825 my $all_foreign_server = join(", ", @foreign_server_list);
2826 daemon_log("0 INFO: found foreign server in config file and DNS: $all_foreign_server", 5);
2828 # add all found foreign servers to known_server
2829 my $act_timestamp = &get_time();
2830 foreach my $foreign_server (@foreign_server_list) {
2831 my $res = $known_server_db->add_dbentry( {table=>$known_server_tn,
2832 primkey=>['hostname'],
2833 hostname=>$foreign_server,
2834 status=>'not_jet_registered',
2835 hostkey=>"none",
2836 timestamp=>$act_timestamp,
2837 } );
2838 }
2841 POE::Component::Server::TCP->new(
2842 Alias => "TCP_SERVER",
2843 Port => $server_port,
2844 ClientInput => sub {
2845 my ($kernel, $input) = @_[KERNEL, ARG0];
2846 push(@tasks, $input);
2847 push(@msgs_to_decrypt, $input);
2848 $kernel->yield("msg_to_decrypt");
2849 },
2850 InlineStates => {
2851 msg_to_decrypt => \&msg_to_decrypt,
2852 next_task => \&next_task,
2853 task_result => \&handle_task_result,
2854 task_done => \&handle_task_done,
2855 task_debug => \&handle_task_debug,
2856 child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!" },
2857 }
2858 );
2860 daemon_log("start socket for incoming xml messages at port '$server_port' ", 1);
2862 # create session for repeatedly checking the job queue for jobs
2863 POE::Session->create(
2864 inline_states => {
2865 _start => \&session_start,
2866 register_at_foreign_servers => \®ister_at_foreign_servers,
2867 sig_handler => \&sig_handler,
2868 next_task => \&next_task,
2869 task_result => \&handle_task_result,
2870 task_done => \&handle_task_done,
2871 task_debug => \&handle_task_debug,
2872 watch_for_next_tasks => \&watch_for_next_tasks,
2873 watch_for_new_messages => \&watch_for_new_messages,
2874 watch_for_delivery_messages => \&watch_for_delivery_messages,
2875 watch_for_done_messages => \&watch_for_done_messages,
2876 watch_for_new_jobs => \&watch_for_new_jobs,
2877 watch_for_done_jobs => \&watch_for_done_jobs,
2878 watch_for_old_known_clients => \&watch_for_old_known_clients,
2879 create_packages_list_db => \&run_create_packages_list_db,
2880 create_fai_server_db => \&run_create_fai_server_db,
2881 create_fai_release_db => \&run_create_fai_release_db,
2882 session_run_result => \&session_run_result,
2883 session_run_debug => \&session_run_debug,
2884 session_run_done => \&session_run_done,
2885 child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!" },
2886 }
2887 );
2890 # import all modules
2891 &import_modules;
2893 # TODO
2894 # check wether all modules are gosa-si valid passwd check
2898 POE::Kernel->run();
2899 exit;