1 #!/usr/bin/perl
2 #===============================================================================
3 #
4 # FILE: gosa-sd
5 #
6 # USAGE: ./gosa-sd
7 #
8 # DESCRIPTION:
9 #
10 # OPTIONS: ---
11 # REQUIREMENTS: libconfig-inifiles-perl libcrypt-rijndael-perl libxml-simple-perl
12 # libdata-dumper-simple-perl libdbd-sqlite3-perl libnet-ldap-perl
13 # libpoe-perl
14 # BUGS: ---
15 # NOTES:
16 # AUTHOR: (Andreas Rettenberger), <rettenberger@gonicus.de>
17 # COMPANY:
18 # VERSION: 1.0
19 # CREATED: 12.09.2007 08:54:41 CEST
20 # REVISION: ---
21 #===============================================================================
24 # TODO
25 #
26 # max_children wird momentan nicht mehr verwendet, jede eingehende nachricht bekommt ein eigenes POE child
28 use strict;
29 use warnings;
30 use Getopt::Long;
31 use Config::IniFiles;
32 use POSIX;
34 use Fcntl;
35 use IO::Socket::INET;
36 use IO::Handle;
37 use IO::Select;
38 use Symbol qw(qualify_to_ref);
39 use Crypt::Rijndael;
40 use MIME::Base64;
41 use Digest::MD5 qw(md5 md5_hex md5_base64);
42 use XML::Simple;
43 use Data::Dumper;
44 use Sys::Syslog qw( :DEFAULT setlogsock);
45 use Cwd;
46 use File::Spec;
47 use File::Basename;
48 use File::Find;
49 use File::Copy;
50 use File::Path;
51 use GOSA::DBsqlite;
52 use GOSA::GosaSupportDaemon;
53 use POE qw(Component::Server::TCP Wheel::Run Filter::Reference);
54 use Net::LDAP;
55 use Net::LDAP::Util qw(:escape);
56 use Time::HiRes qw( usleep);
58 my $modules_path = "/usr/lib/gosa-si/modules";
59 use lib "/usr/lib/gosa-si/modules";
61 # revision number of server and program name
62 my $server_version = '$HeadURL: https://oss.gonicus.de/repositories/gosa/trunk/gosa-si/gosa-si-server $:$Rev: 10826 $';
63 my $server_headURL;
64 my $server_revision;
65 my $server_status;
66 our $prg= basename($0);
68 our $global_kernel;
69 my (%cfg_defaults, $foreground, $verbose, $ping_timeout);
70 my ($bus_activ, $bus, $msg_to_bus, $bus_cipher);
71 my ($server);
72 my ($gosa_server, $job_queue_timeout, $job_queue_loop_delay);
73 my ($messaging_db_loop_delay);
74 my ($known_modules);
75 my ($pid_file, $procid, $pid, $log_file);
76 my ($arp_activ, $arp_fifo);
77 my ($xml);
78 my $sources_list;
79 my $max_clients;
80 my %repo_files=();
81 my $repo_path;
82 my %repo_dirs=();
83 # variables declared in config file are always set to 'our'
84 our (%cfg_defaults, $log_file, $pid_file,
85 $server_ip, $server_port, $ClientPackages_key,
86 $arp_activ, $gosa_unit_tag,
87 $GosaPackages_key, $gosa_ip, $gosa_port, $gosa_timeout,
88 $foreign_server_string, $server_domain, $ServerPackages_key, $foreign_servers_register_delay,
89 );
91 # additional variable which should be globaly accessable
92 our $server_address;
93 our $server_mac_address;
94 our $bus_address;
95 our $gosa_address;
96 our $no_bus;
97 our $no_arp;
98 our $verbose;
99 our $forground;
100 our $cfg_file;
101 our ($ldap_uri, $ldap_base, $ldap_admin_dn, $ldap_admin_password, $ldap_server_dn);
104 # specifies the verbosity of the daemon_log
105 $verbose = 0 ;
107 # if foreground is not null, script will be not forked to background
108 $foreground = 0 ;
110 # specifies the timeout seconds while checking the online status of a registrating client
111 $ping_timeout = 5;
113 $no_bus = 0;
114 $bus_activ = "true";
115 $no_arp = 0;
116 my $packages_list_under_construction = "/tmp/packages_list_creation_in_progress";
117 my @packages_list_statements;
118 my $watch_for_new_jobs_in_progress = 0;
120 # holds all incoming decrypted messages
121 our $incoming_db;
122 our $incoming_tn = 'incoming';
123 my $incoming_file_name;
124 my @incoming_col_names = ("id INTEGER PRIMARY KEY",
125 "timestamp DEFAULT 'none'",
126 "headertag DEFAULT 'none'",
127 "targettag DEFAULT 'none'",
128 "xmlmessage DEFAULT 'none'",
129 "module DEFAULT 'none'",
130 );
132 # holds all gosa jobs
133 our $job_db;
134 our $job_queue_tn = 'jobs';
135 my $job_queue_file_name;
136 my @job_queue_col_names = ("id INTEGER PRIMARY KEY",
137 "timestamp DEFAULT 'none'",
138 "status DEFAULT 'none'",
139 "result DEFAULT 'none'",
140 "progress DEFAULT 'none'",
141 "headertag DEFAULT 'none'",
142 "targettag DEFAULT 'none'",
143 "xmlmessage DEFAULT 'none'",
144 "macaddress DEFAULT 'none'",
145 "plainname DEFAULT 'none'",
146 );
148 # holds all other gosa-sd as well as the gosa-sd-bus
149 our $known_server_db;
150 our $known_server_tn = "known_server";
151 my $known_server_file_name;
152 my @known_server_col_names = ("hostname", "status", "hostkey", "timestamp");
154 # holds all registrated clients
155 our $known_clients_db;
156 our $known_clients_tn = "known_clients";
157 my $known_clients_file_name;
158 my @known_clients_col_names = ("hostname", "status", "hostkey", "timestamp", "macaddress", "events", "keylifetime");
160 # holds all registered clients at a foreign server
161 our $foreign_clients_db;
162 our $foreign_clients_tn = "foreign_clients";
163 my $foreign_clients_file_name;
164 my @foreign_clients_col_names = ("hostname", "macaddress", "regserver", "timestamp");
166 # holds all logged in user at each client
167 our $login_users_db;
168 our $login_users_tn = "login_users";
169 my $login_users_file_name;
170 my @login_users_col_names = ("client", "user", "timestamp");
172 # holds all fai server, the debian release and tag
173 our $fai_server_db;
174 our $fai_server_tn = "fai_server";
175 my $fai_server_file_name;
176 our @fai_server_col_names = ("timestamp", "server", "release", "sections", "tag");
178 our $fai_release_db;
179 our $fai_release_tn = "fai_release";
180 my $fai_release_file_name;
181 our @fai_release_col_names = ("timestamp", "release", "class", "type", "state");
183 # holds all packages available from different repositories
184 our $packages_list_db;
185 our $packages_list_tn = "packages_list";
186 my $packages_list_file_name;
187 our @packages_list_col_names = ("distribution", "package", "version", "section", "description", "template", "timestamp");
188 my $outdir = "/tmp/packages_list_db";
189 my $arch = "i386";
191 # holds all messages which should be delivered to a user
192 our $messaging_db;
193 our $messaging_tn = "messaging";
194 our @messaging_col_names = ("id INTEGER", "subject", "message_from", "message_to",
195 "flag", "direction", "delivery_time", "message", "timestamp" );
196 my $messaging_file_name;
198 # path to directory to store client install log files
199 our $client_fai_log_dir = "/var/log/fai";
201 # queue which stores taskes until one of the $max_children children are ready to process the task
202 my @tasks = qw();
203 my @msgs_to_decrypt = qw();
204 my $max_children = 2;
207 %cfg_defaults = (
208 "general" => {
209 "log-file" => [\$log_file, "/var/run/".$prg.".log"],
210 "pid-file" => [\$pid_file, "/var/run/".$prg.".pid"],
211 },
212 "bus" => {
213 "activ" => [\$bus_activ, "true"],
214 },
215 "server" => {
216 "port" => [\$server_port, "20081"],
217 "known-clients" => [\$known_clients_file_name, '/var/lib/gosa-si/clients.db' ],
218 "known-servers" => [\$known_server_file_name, '/var/lib/gosa-si/servers.db'],
219 "incoming" => [\$incoming_file_name, '/var/lib/gosa-si/incoming.db'],
220 "login-users" => [\$login_users_file_name, '/var/lib/gosa-si/users.db'],
221 "fai-server" => [\$fai_server_file_name, '/var/lib/gosa-si/fai_server.db'],
222 "fai-release" => [\$fai_release_file_name, '/var/lib/gosa-si/fai_release.db'],
223 "packages-list" => [\$packages_list_file_name, '/var/lib/gosa-si/packages.db'],
224 "messaging" => [\$messaging_file_name, '/var/lib/gosa-si/messaging.db'],
225 "foreign-clients" => [\$foreign_clients_file_name, '/var/lib/gosa-si/foreign_clients.db'],
226 "source-list" => [\$sources_list, '/etc/apt/sources.list'],
227 "repo-path" => [\$repo_path, '/srv/www/repository'],
228 "ldap-uri" => [\$ldap_uri, ""],
229 "ldap-base" => [\$ldap_base, ""],
230 "ldap-admin-dn" => [\$ldap_admin_dn, ""],
231 "ldap-admin-password" => [\$ldap_admin_password, ""],
232 "gosa-unit-tag" => [\$gosa_unit_tag, ""],
233 "max-clients" => [\$max_clients, 10],
234 },
235 "GOsaPackages" => {
236 "ip" => [\$gosa_ip, "0.0.0.0"],
237 "port" => [\$gosa_port, "20082"],
238 "job-queue" => [\$job_queue_file_name, '/var/lib/gosa-si/jobs.db'],
239 "job-queue-loop-delay" => [\$job_queue_loop_delay, 3],
240 "messaging-db-loop-delay" => [\$messaging_db_loop_delay, 3],
241 "key" => [\$GosaPackages_key, "none"],
242 },
243 "ClientPackages" => {
244 "key" => [\$ClientPackages_key, "none"],
245 },
246 "ServerPackages"=> {
247 "address" => [\$foreign_server_string, ""],
248 "domain" => [\$server_domain, ""],
249 "key" => [\$ServerPackages_key, "none"],
250 "key-lifetime" => [\$foreign_servers_register_delay, 120],
251 }
252 );
255 #=== FUNCTION ================================================================
256 # NAME: usage
257 # PARAMETERS: nothing
258 # RETURNS: nothing
259 # DESCRIPTION: print out usage text to STDERR
260 #===============================================================================
261 sub usage {
262 print STDERR << "EOF" ;
263 usage: $prg [-hvf] [-c config]
265 -h : this (help) message
266 -c <file> : config file
267 -f : foreground, process will not be forked to background
268 -v : be verbose (multiple to increase verbosity)
269 -no-bus : starts $prg without connection to bus
270 -no-arp : starts $prg without connection to arp module
272 EOF
273 print "\n" ;
274 }
277 #=== FUNCTION ================================================================
278 # NAME: read_configfile
279 # PARAMETERS: cfg_file - string -
280 # RETURNS: nothing
281 # DESCRIPTION: read cfg_file and set variables
282 #===============================================================================
283 sub read_configfile {
284 my $cfg;
285 if( defined( $cfg_file) && ( (-s $cfg_file) > 0 )) {
286 if( -r $cfg_file ) {
287 $cfg = Config::IniFiles->new( -file => $cfg_file );
288 } else {
289 print STDERR "Couldn't read config file!\n";
290 }
291 } else {
292 $cfg = Config::IniFiles->new() ;
293 }
294 foreach my $section (keys %cfg_defaults) {
295 foreach my $param (keys %{$cfg_defaults{ $section }}) {
296 my $pinfo = $cfg_defaults{ $section }{ $param };
297 ${@$pinfo[ 0 ]} = $cfg->val( $section, $param, @$pinfo[ 1 ] );
298 }
299 }
300 }
303 #=== FUNCTION ================================================================
304 # NAME: logging
305 # PARAMETERS: level - string - default 'info'
306 # msg - string -
307 # facility - string - default 'LOG_DAEMON'
308 # RETURNS: nothing
309 # DESCRIPTION: function for logging
310 #===============================================================================
311 sub daemon_log {
312 # log into log_file
313 my( $msg, $level ) = @_;
314 if(not defined $msg) { return }
315 if(not defined $level) { $level = 1 }
316 if(defined $log_file){
317 open(LOG_HANDLE, ">>$log_file");
318 chmod 0600, $log_file;
319 if(not defined open( LOG_HANDLE, ">>$log_file" )) {
320 print STDERR "cannot open $log_file: $!";
321 return }
322 chomp($msg);
323 $msg =~s/\n//g; # no newlines are allowed in log messages, this is important for later log parsing
324 if($level <= $verbose){
325 my ($seconds, $minutes, $hours, $monthday, $month,
326 $year, $weekday, $yearday, $sommertime) = localtime(time);
327 $hours = $hours < 10 ? $hours = "0".$hours : $hours;
328 $minutes = $minutes < 10 ? $minutes = "0".$minutes : $minutes;
329 $seconds = $seconds < 10 ? $seconds = "0".$seconds : $seconds;
330 my @monthnames = ("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec");
331 $month = $monthnames[$month];
332 $monthday = $monthday < 10 ? $monthday = "0".$monthday : $monthday;
333 $year+=1900;
334 my $name = $prg;
336 my $log_msg = "$month $monthday $hours:$minutes:$seconds $name $msg\n";
337 print LOG_HANDLE $log_msg;
338 if( $foreground ) {
339 print STDERR $log_msg;
340 }
341 }
342 close( LOG_HANDLE );
343 }
344 }
347 #=== FUNCTION ================================================================
348 # NAME: check_cmdline_param
349 # PARAMETERS: nothing
350 # RETURNS: nothing
351 # DESCRIPTION: validates commandline parameter
352 #===============================================================================
353 sub check_cmdline_param () {
354 my $err_config;
355 my $err_counter = 0;
356 if(not defined($cfg_file)) {
357 $cfg_file = "/etc/gosa-si/server.conf";
358 if(! -r $cfg_file) {
359 $err_config = "please specify a config file";
360 $err_counter += 1;
361 }
362 }
363 if( $err_counter > 0 ) {
364 &usage( "", 1 );
365 if( defined( $err_config)) { print STDERR "$err_config\n"}
366 print STDERR "\n";
367 exit( -1 );
368 }
369 }
372 #=== FUNCTION ================================================================
373 # NAME: check_pid
374 # PARAMETERS: nothing
375 # RETURNS: nothing
376 # DESCRIPTION: handels pid processing
377 #===============================================================================
378 sub check_pid {
379 $pid = -1;
380 # Check, if we are already running
381 if( open(LOCK_FILE, "<$pid_file") ) {
382 $pid = <LOCK_FILE>;
383 if( defined $pid ) {
384 chomp( $pid );
385 if( -f "/proc/$pid/stat" ) {
386 my($stat) = `cat /proc/$pid/stat` =~ m/$pid \((.+)\).*/;
387 if( $stat ) {
388 daemon_log("ERROR: Already running",1);
389 close( LOCK_FILE );
390 exit -1;
391 }
392 }
393 }
394 close( LOCK_FILE );
395 unlink( $pid_file );
396 }
398 # create a syslog msg if it is not to possible to open PID file
399 if (not sysopen(LOCK_FILE, $pid_file, O_WRONLY|O_CREAT|O_EXCL, 0644)) {
400 my($msg) = "Couldn't obtain lockfile '$pid_file' ";
401 if (open(LOCK_FILE, '<', $pid_file)
402 && ($pid = <LOCK_FILE>))
403 {
404 chomp($pid);
405 $msg .= "(PID $pid)\n";
406 } else {
407 $msg .= "(unable to read PID)\n";
408 }
409 if( ! ($foreground) ) {
410 openlog( $0, "cons,pid", "daemon" );
411 syslog( "warning", $msg );
412 closelog();
413 }
414 else {
415 print( STDERR " $msg " );
416 }
417 exit( -1 );
418 }
419 }
421 #=== FUNCTION ================================================================
422 # NAME: import_modules
423 # PARAMETERS: module_path - string - abs. path to the directory the modules
424 # are stored
425 # RETURNS: nothing
426 # DESCRIPTION: each file in module_path which ends with '.pm' and activation
427 # state is on is imported by "require 'file';"
428 #===============================================================================
429 sub import_modules {
430 daemon_log(" ", 1);
432 if (not -e $modules_path) {
433 daemon_log("0 ERROR: cannot find directory or directory is not readable: $modules_path", 1);
434 }
436 opendir (DIR, $modules_path) or die "ERROR while loading modules from directory $modules_path : $!\n";
437 while (defined (my $file = readdir (DIR))) {
438 if (not $file =~ /(\S*?).pm$/) {
439 next;
440 }
441 my $mod_name = $1;
443 if( $file =~ /ArpHandler.pm/ ) {
444 if( $no_arp > 0 ) {
445 next;
446 }
447 }
449 eval { require $file; };
450 if ($@) {
451 daemon_log("0 ERROR: gosa-si-server could not load module $file", 1);
452 daemon_log("$@", 5);
453 } else {
454 my $info = eval($mod_name.'::get_module_info()');
455 # Only load module if get_module_info() returns a non-null object
456 if( $info ) {
457 my ($input_address, $input_key, $input, $input_active, $input_type) = @{$info};
458 $known_modules->{$mod_name} = $info;
459 daemon_log("0 INFO: module $mod_name loaded", 5);
460 }
461 }
462 }
463 close (DIR);
464 }
467 #=== FUNCTION ================================================================
468 # NAME: sig_int_handler
469 # PARAMETERS: signal - string - signal arose from system
470 # RETURNS: noting
471 # DESCRIPTION: handels tasks to be done befor signal becomes active
472 #===============================================================================
473 sub sig_int_handler {
474 my ($signal) = @_;
476 # if (defined($ldap_handle)) {
477 # $ldap_handle->disconnect;
478 # }
479 # TODO alle verbliebenden ldap verbindungen aus allen heaps beenden
482 daemon_log("shutting down gosa-si-server", 1);
483 system("kill `ps -C gosa-si-server-nobus -o pid=`");
484 }
485 $SIG{INT} = \&sig_int_handler;
488 sub check_key_and_xml_validity {
489 my ($crypted_msg, $module_key, $session_id) = @_;
490 my $msg;
491 my $msg_hash;
492 my $error_string;
493 eval{
494 $msg = &decrypt_msg($crypted_msg, $module_key);
496 if ($msg =~ /<xml>/i){
497 $msg =~ s/\s+/ /g; # just for better daemon_log
498 daemon_log("$session_id DEBUG: decrypted_msg: \n$msg", 8);
499 $msg_hash = $xml->XMLin($msg, ForceArray=>1);
501 ##############
502 # check header
503 if( not exists $msg_hash->{'header'} ) { die "no header specified"; }
504 my $header_l = $msg_hash->{'header'};
505 if( 1 > @{$header_l} ) { die 'empty header tag'; }
506 if( 1 < @{$header_l} ) { die 'more than one header specified'; }
507 my $header = @{$header_l}[0];
508 if( 0 == length $header) { die 'empty string in header tag'; }
510 ##############
511 # check source
512 if( not exists $msg_hash->{'source'} ) { die "no source specified"; }
513 my $source_l = $msg_hash->{'source'};
514 if( 1 > @{$source_l} ) { die 'empty source tag'; }
515 if( 1 < @{$source_l} ) { die 'more than one source specified'; }
516 my $source = @{$source_l}[0];
517 if( 0 == length $source) { die 'source error'; }
519 ##############
520 # check target
521 if( not exists $msg_hash->{'target'} ) { die "no target specified"; }
522 my $target_l = $msg_hash->{'target'};
523 if( 1 > @{$target_l} ) { die 'empty target tag'; }
524 }
525 };
526 if($@) {
527 daemon_log("$session_id DEBUG: do not understand the message: $@", 7);
528 $msg = undef;
529 $msg_hash = undef;
530 }
532 return ($msg, $msg_hash);
533 }
536 sub check_outgoing_xml_validity {
537 my ($msg) = @_;
539 my $msg_hash;
540 eval{
541 $msg_hash = $xml->XMLin($msg, ForceArray=>1);
543 ##############
544 # check header
545 my $header_l = $msg_hash->{'header'};
546 if( 1 != @{$header_l} ) {
547 die 'no or more than one headers specified';
548 }
549 my $header = @{$header_l}[0];
550 if( 0 == length $header) {
551 die 'header has length 0';
552 }
554 ##############
555 # check source
556 my $source_l = $msg_hash->{'source'};
557 if( 1 != @{$source_l} ) {
558 die 'no or more than 1 sources specified';
559 }
560 my $source = @{$source_l}[0];
561 if( 0 == length $source) {
562 die 'source has length 0';
563 }
564 unless( $source =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
565 $source =~ /^GOSA$/i ) {
566 die "source '$source' is neither a complete ip-address with port nor 'GOSA'";
567 }
569 ##############
570 # check target
571 my $target_l = $msg_hash->{'target'};
572 if( 0 == @{$target_l} ) {
573 die "no targets specified";
574 }
575 foreach my $target (@$target_l) {
576 if( 0 == length $target) {
577 die "target has length 0";
578 }
579 unless( $target =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
580 $target =~ /^GOSA$/i ||
581 $target =~ /^\*$/ ||
582 $target =~ /KNOWN_SERVER/i ||
583 $target =~ /JOBDB/i ||
584 $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 ){
585 die "target '$target' is not a complete ip-address with port or a valid target name or a mac-address";
586 }
587 }
588 };
589 if($@) {
590 daemon_log("WARNING: outgoing msg is not gosa-si envelope conform", 5);
591 daemon_log("$@ ".(defined($msg) && length($msg)>0)?$msg:"Empty Message", 8);
592 $msg_hash = undef;
593 }
595 return ($msg_hash);
596 }
599 sub input_from_known_server {
600 my ($input, $remote_ip, $session_id) = @_ ;
601 my ($msg, $msg_hash, $module);
603 my $sql_statement= "SELECT * FROM known_server";
604 my $query_res = $known_server_db->select_dbentry( $sql_statement );
606 while( my ($hit_num, $hit) = each %{ $query_res } ) {
607 my $host_name = $hit->{hostname};
608 if( not $host_name =~ "^$remote_ip") {
609 next;
610 }
611 my $host_key = $hit->{hostkey};
612 daemon_log("$session_id DEBUG: input_from_known_server: host_name: $host_name", 7);
613 daemon_log("$session_id DEBUG: input_from_known_server: host_key: $host_key", 7);
615 # check if module can open msg envelope with module key
616 my ($tmp_msg, $tmp_msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
617 if( (!$tmp_msg) || (!$tmp_msg_hash) ) {
618 daemon_log("$session_id DEBUG: input_from_known_server: deciphering raise error", 7);
619 daemon_log("$@", 8);
620 next;
621 }
622 else {
623 $msg = $tmp_msg;
624 $msg_hash = $tmp_msg_hash;
625 $module = "ServerPackages";
626 last;
627 }
628 }
630 if( (!$msg) || (!$msg_hash) || (!$module) ) {
631 daemon_log("$session_id DEBUG: Incoming message is not from a known server", 7);
632 }
634 return ($msg, $msg_hash, $module);
635 }
638 sub input_from_known_client {
639 my ($input, $remote_ip, $session_id) = @_ ;
640 my ($msg, $msg_hash, $module);
642 my $sql_statement= "SELECT * FROM known_clients";
643 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
644 while( my ($hit_num, $hit) = each %{ $query_res } ) {
645 my $host_name = $hit->{hostname};
646 if( not $host_name =~ /^$remote_ip:\d*$/) {
647 next;
648 }
649 my $host_key = $hit->{hostkey};
650 &daemon_log("$session_id DEBUG: input_from_known_client: host_name: $host_name", 7);
651 &daemon_log("$session_id DEBUG: input_from_known_client: host_key: $host_key", 7);
653 # check if module can open msg envelope with module key
654 ($msg, $msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
656 if( (!$msg) || (!$msg_hash) ) {
657 &daemon_log("$session_id DEGUG: input_from_known_client: deciphering raise error", 7);
658 &daemon_log("$@", 8);
659 next;
660 }
661 else {
662 $module = "ClientPackages";
663 last;
664 }
665 }
667 if( (!$msg) || (!$msg_hash) || (!$module) ) {
668 &daemon_log("$session_id DEBUG: Incoming message is not from a known client", 7);
669 }
671 return ($msg, $msg_hash, $module);
672 }
675 sub input_from_unknown_host {
676 no strict "refs";
677 my ($input, $session_id) = @_ ;
678 my ($msg, $msg_hash, $module);
679 my $error_string;
681 my %act_modules = %$known_modules;
683 while( my ($mod, $info) = each(%act_modules)) {
685 # check a key exists for this module
686 my $module_key = ${$mod."_key"};
687 if( not defined $module_key ) {
688 if( $mod eq 'ArpHandler' ) {
689 next;
690 }
691 daemon_log("$session_id ERROR: no key specified in config file for $mod", 1);
692 next;
693 }
694 daemon_log("$session_id DEBUG: $mod: $module_key", 7);
696 # check if module can open msg envelope with module key
697 ($msg, $msg_hash) = &check_key_and_xml_validity($input, $module_key, $session_id);
698 if( (not defined $msg) || (not defined $msg_hash) ) {
699 next;
700 }
701 else {
702 $module = $mod;
703 last;
704 }
705 }
707 if( (!$msg) || (!$msg_hash) || (!$module)) {
708 daemon_log("$session_id DEBUG: Incoming message is not from an unknown host", 7);
709 }
711 return ($msg, $msg_hash, $module);
712 }
715 sub create_ciphering {
716 my ($passwd) = @_;
717 if((!defined($passwd)) || length($passwd)==0) {
718 $passwd = "";
719 }
720 $passwd = substr(md5_hex("$passwd") x 32, 0, 32);
721 my $iv = substr(md5_hex('GONICUS GmbH'),0, 16);
722 my $my_cipher = Crypt::Rijndael->new($passwd , Crypt::Rijndael::MODE_CBC());
723 $my_cipher->set_iv($iv);
724 return $my_cipher;
725 }
728 sub encrypt_msg {
729 my ($msg, $key) = @_;
730 my $my_cipher = &create_ciphering($key);
731 my $len;
732 {
733 use bytes;
734 $len= 16-length($msg)%16;
735 }
736 $msg = "\0"x($len).$msg;
737 $msg = $my_cipher->encrypt($msg);
738 chomp($msg = &encode_base64($msg));
739 # there are no newlines allowed inside msg
740 $msg=~ s/\n//g;
741 return $msg;
742 }
745 sub decrypt_msg {
747 my ($msg, $key) = @_ ;
748 $msg = &decode_base64($msg);
749 my $my_cipher = &create_ciphering($key);
750 $msg = $my_cipher->decrypt($msg);
751 $msg =~ s/\0*//g;
752 return $msg;
753 }
756 sub get_encrypt_key {
757 my ($target) = @_ ;
758 my $encrypt_key;
759 my $error = 0;
761 # target can be in known_server
762 if( not defined $encrypt_key ) {
763 my $sql_statement= "SELECT * FROM known_server WHERE hostname='$target'";
764 my $query_res = $known_server_db->select_dbentry( $sql_statement );
765 while( my ($hit_num, $hit) = each %{ $query_res } ) {
766 my $host_name = $hit->{hostname};
767 if( $host_name ne $target ) {
768 next;
769 }
770 $encrypt_key = $hit->{hostkey};
771 last;
772 }
773 }
775 # target can be in known_client
776 if( not defined $encrypt_key ) {
777 my $sql_statement= "SELECT * FROM known_clients WHERE hostname='$target'";
778 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
779 while( my ($hit_num, $hit) = each %{ $query_res } ) {
780 my $host_name = $hit->{hostname};
781 if( $host_name ne $target ) {
782 next;
783 }
784 $encrypt_key = $hit->{hostkey};
785 last;
786 }
787 }
789 return $encrypt_key;
790 }
793 #=== FUNCTION ================================================================
794 # NAME: open_socket
795 # PARAMETERS: PeerAddr string something like 192.168.1.1 or 192.168.1.1:10000
796 # [PeerPort] string necessary if port not appended by PeerAddr
797 # RETURNS: socket IO::Socket::INET
798 # DESCRIPTION: open a socket to PeerAddr
799 #===============================================================================
800 sub open_socket {
801 my ($PeerAddr, $PeerPort) = @_ ;
802 if(defined($PeerPort)){
803 $PeerAddr = $PeerAddr.":".$PeerPort;
804 }
805 my $socket;
806 $socket = new IO::Socket::INET(PeerAddr => $PeerAddr,
807 Porto => "tcp",
808 Type => SOCK_STREAM,
809 Timeout => 5,
810 );
811 if(not defined $socket) {
812 return;
813 }
814 # &daemon_log("DEBUG: open_socket: $PeerAddr", 7);
815 return $socket;
816 }
819 #=== FUNCTION ================================================================
820 # NAME: get_ip
821 # PARAMETERS: interface name (i.e. eth0)
822 # RETURNS: (ip address)
823 # DESCRIPTION: Uses ioctl to get ip address directly from system.
824 #===============================================================================
825 sub get_ip {
826 my $ifreq= shift;
827 my $result= "";
828 my $SIOCGIFADDR= 0x8915; # man 2 ioctl_list
829 my $proto= getprotobyname('ip');
831 socket SOCKET, PF_INET, SOCK_DGRAM, $proto
832 or die "socket: $!";
834 if(ioctl SOCKET, $SIOCGIFADDR, $ifreq) {
835 my ($if, $sin) = unpack 'a16 a16', $ifreq;
836 my ($port, $addr) = sockaddr_in $sin;
837 my $ip = inet_ntoa $addr;
839 if ($ip && length($ip) > 0) {
840 $result = $ip;
841 }
842 }
844 return $result;
845 }
848 sub get_local_ip_for_remote_ip {
849 my $remote_ip= shift;
850 my $result="0.0.0.0";
852 if($remote_ip =~ /^(\d\d?\d?\.){3}\d\d?\d?$/) {
853 if($remote_ip eq "127.0.0.1") {
854 $result = "127.0.0.1";
855 } else {
856 my $PROC_NET_ROUTE= ('/proc/net/route');
858 open(PROC_NET_ROUTE, "<$PROC_NET_ROUTE")
859 or die "Could not open $PROC_NET_ROUTE";
861 my @ifs = <PROC_NET_ROUTE>;
863 close(PROC_NET_ROUTE);
865 # Eat header line
866 shift @ifs;
867 chomp @ifs;
868 foreach my $line(@ifs) {
869 my ($Iface,$Destination,$Gateway,$Flags,$RefCnt,$Use,$Metric,$Mask,$MTU,$Window,$IRTT)=split(/\s/, $line);
870 my $destination;
871 my $mask;
872 my ($d,$c,$b,$a)=unpack('a2 a2 a2 a2', $Destination);
873 $destination= sprintf("%d.%d.%d.%d", hex($a), hex($b), hex($c), hex($d));
874 ($d,$c,$b,$a)=unpack('a2 a2 a2 a2', $Mask);
875 $mask= sprintf("%d.%d.%d.%d", hex($a), hex($b), hex($c), hex($d));
876 if(new NetAddr::IP($remote_ip)->within(new NetAddr::IP($destination, $mask))) {
877 # destination matches route, save mac and exit
878 $result= &get_ip($Iface);
879 last;
880 }
881 }
882 }
883 } else {
884 daemon_log("get_local_ip_for_remote_ip was called with a non-ip parameter: $remote_ip", 1);
885 }
886 return $result;
887 }
890 sub send_msg_to_target {
891 my ($msg, $address, $encrypt_key, $msg_header, $session_id) = @_ ;
892 my $error = 0;
893 my $header;
894 my $timestamp = &get_time();
895 my $new_status;
896 my $act_status;
897 my ($sql_statement, $res);
899 if( $msg_header ) {
900 $header = "'$msg_header'-";
901 } else {
902 $header = "";
903 }
905 # Patch the source ip
906 if($msg =~ /<source>0\.0\.0\.0:\d*?<\/source>/) {
907 my $remote_ip = &get_local_ip_for_remote_ip(sprintf("%s", $address =~ /^([0-9\.]*?):.*$/));
908 $msg =~ s/<source>(0\.0\.0\.0):(\d*?)<\/source>/<source>$remote_ip:$2<\/source>/s;
909 }
911 # encrypt xml msg
912 my $crypted_msg = &encrypt_msg($msg, $encrypt_key);
914 # opensocket
915 my $socket = &open_socket($address);
916 if( !$socket ) {
917 daemon_log("$session_id ERROR: cannot send ".$header."msg to $address , host not reachable", 1);
918 $error++;
919 }
921 if( $error == 0 ) {
922 # send xml msg
923 print $socket $crypted_msg."\n";
925 daemon_log("$session_id INFO: send ".$header."msg to $address", 5);
926 daemon_log("DEBUG: message:\n$msg", 9);
928 }
930 # close socket in any case
931 if( $socket ) {
932 close $socket;
933 }
935 if( $error > 0 ) { $new_status = "down"; }
936 else { $new_status = $msg_header; }
939 # known_clients
940 $sql_statement = "SELECT * FROM known_clients WHERE hostname='$address'";
941 $res = $known_clients_db->select_dbentry($sql_statement);
942 if( keys(%$res) > 0) {
943 $act_status = $res->{1}->{'status'};
944 if ($act_status eq "down" && $new_status eq "down") {
945 $sql_statement = "DELETE FROM known_clients WHERE hostname='$address'";
946 $res = $known_clients_db->del_dbentry($sql_statement);
947 daemon_log("$session_id WARNING: failed 2x to send msg to host '$address', delete host from known_clients", 3);
948 } else {
949 $sql_statement = "UPDATE known_clients SET status='$new_status', timestamp='$timestamp' WHERE hostname='$address'";
950 $res = $known_clients_db->update_dbentry($sql_statement);
951 if($new_status eq "down"){
952 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
953 } else {
954 daemon_log("$session_id INFO: set '$address' from status '$act_status' to '$new_status'", 5);
955 }
956 }
957 }
959 # known_server
960 $sql_statement = "SELECT * FROM $known_server_tn WHERE hostname='$address'";
961 $res = $known_server_db->select_dbentry($sql_statement);
962 if( keys(%$res) > 0 ) {
963 $act_status = $res->{1}->{'status'};
964 if ($act_status eq "down" && $new_status eq "down") {
965 $sql_statement = "DELETE FROM known_server WHERE hostname='$address'";
966 $res = $known_server_db->del_dbentry($sql_statement);
967 daemon_log("$session_id WARNING: failed 2x to send a message to host '$address', delete host from known_server", 3);
968 }
969 else {
970 $sql_statement = "UPDATE known_server SET status='$new_status', timestamp='$timestamp' WHERE hostname='$address'";
971 $res = $known_server_db->update_dbentry($sql_statement);
972 if($new_status eq "down"){
973 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
974 }
975 else {
976 daemon_log("$session_id INFO: set '$address' from status '$act_status' to '$new_status'", 5);
977 }
978 }
979 }
980 return $error;
981 }
984 sub update_jobdb_status_for_send_msgs {
985 my ($answer, $error) = @_;
986 if( $answer =~ /<jobdb_id>(\d+)<\/jobdb_id>/ ) {
987 my $jobdb_id = $1;
989 # sending msg faild
990 if( $error ) {
991 if (not $answer =~ /<header>trigger_action_reinstall<\/header>/) {
992 my $sql_statement = "UPDATE $job_queue_tn ".
993 "SET status='error', result='can not deliver msg, please consult log file' ".
994 "WHERE id=$jobdb_id";
995 my $res = $job_db->update_dbentry($sql_statement);
996 }
998 # sending msg was successful
999 } else {
1000 my $sql_statement = "UPDATE $job_queue_tn ".
1001 "SET status='done' ".
1002 "WHERE id=$jobdb_id AND status='processed'";
1003 my $res = $job_db->update_dbentry($sql_statement);
1004 }
1005 }
1006 }
1008 sub _start {
1009 my ($kernel) = $_[KERNEL];
1010 &trigger_db_loop($kernel);
1011 $global_kernel = $kernel;
1012 $kernel->yield('register_at_foreign_servers');
1013 $kernel->yield('create_fai_server_db', $fai_server_tn );
1014 $kernel->yield('create_fai_release_db', $fai_release_tn );
1015 $kernel->sig(USR1 => "sig_handler");
1016 $kernel->sig(USR2 => "create_packages_list_db");
1017 }
1019 sub sig_handler {
1020 my ($kernel, $signal) = @_[KERNEL, ARG0] ;
1021 daemon_log("0 INFO got signal '$signal'", 1);
1022 $kernel->sig_handled();
1023 return;
1024 }
1027 sub msg_to_decrypt {
1028 my ($session, $heap) = @_[SESSION, HEAP];
1029 my $session_id = $session->ID;
1030 my ($msg, $msg_hash, $module);
1031 my $error = 0;
1033 # hole neue msg aus @msgs_to_decrypt
1034 my $next_msg = shift @msgs_to_decrypt;
1036 # entschlüssle sie
1038 # msg is from a new client or gosa
1039 ($msg, $msg_hash, $module) = &input_from_unknown_host($next_msg, $session_id);
1040 # msg is from a gosa-si-server or gosa-si-bus
1041 if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1042 ($msg, $msg_hash, $module) = &input_from_known_server($next_msg, $heap->{'remote_ip'}, $session_id);
1043 }
1044 # msg is from a gosa-si-client
1045 if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1046 ($msg, $msg_hash, $module) = &input_from_known_client($next_msg, $heap->{'remote_ip'}, $session_id);
1047 }
1048 # an error occurred
1049 if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1050 # if an incoming msg could not be decrypted (maybe a wrong key), send client a ping. If the client
1051 # could not understand a msg from its server the client cause a re-registering process
1052 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);
1053 my $sql_statement = "SELECT * FROM $main::known_clients_tn WHERE (hostname LIKE '".$heap->{'remote_ip'}."%')";
1054 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1055 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1056 my $host_name = $hit->{'hostname'};
1057 my $host_key = $hit->{'hostkey'};
1058 my $ping_msg = "<xml> <header>gosa_ping</header> <source>$server_address</source> <target>$host_name</target></xml>";
1059 my $error = &send_msg_to_target($ping_msg, $host_name, $host_key, "gosa_ping", $session_id);
1060 &update_jobdb_status_for_send_msgs($ping_msg, $error);
1061 }
1062 $error++;
1063 }
1065 # add message to incoming_db
1066 if( $error == 0) {
1067 my $header = @{$msg_hash->{'header'}}[0];
1068 my $target = @{$msg_hash->{'target'}}[0];
1069 my $res = $incoming_db->add_dbentry( {table=>$incoming_tn,
1070 primkey=>[],
1071 headertag=>$header,
1072 targettag=>$target,
1073 xmlmessage=>$msg,
1074 timestamp=>&get_time,
1075 module=>$module,
1076 } );
1077 if ($res != 0) {
1078 # TODO ist das mit $! so ok???
1079 #&daemon_log("$session_id ERROR: cannot add message to incoming.db: $!", 1);
1080 }
1081 }
1083 }
1086 sub next_task {
1087 my ($session, $heap) = @_[SESSION, HEAP];
1088 my $task = POE::Wheel::Run->new(
1089 Program => sub { process_task($session, $heap) },
1090 StdioFilter => POE::Filter::Reference->new(),
1091 StdoutEvent => "task_result",
1092 StderrEvent => "task_debug",
1093 CloseEvent => "task_done",
1094 );
1096 $heap->{task}->{ $task->ID } = $task;
1097 }
1099 sub handle_task_result {
1100 my ($kernel, $heap, $result) = @_[KERNEL, HEAP, ARG0];
1101 my $client_answer = $result->{'answer'};
1102 if( $client_answer =~ s/session_id=(\d+)$// ) {
1103 my $session_id = $1;
1104 if( defined $session_id ) {
1105 my $session_reference = $kernel->ID_id_to_session($session_id);
1106 if( defined $session_reference ) {
1107 $heap = $session_reference->get_heap();
1108 }
1109 }
1111 if(exists $heap->{'client'}) {
1112 $heap->{'client'}->put($client_answer);
1113 }
1114 }
1115 $kernel->sig(CHLD => "child_reap");
1116 }
1118 sub handle_task_debug {
1119 my $result = $_[ARG0];
1120 print STDERR "$result\n";
1121 }
1123 sub handle_task_done {
1124 my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
1125 delete $heap->{task}->{$task_id};
1126 }
1128 sub process_task {
1129 no strict "refs";
1130 my ($session, $heap, $input) = @_;
1131 my $session_id = $session->ID;
1132 my $error = 0;
1133 my $answer_l;
1134 my ($answer_header, @answer_target_l, $answer_source);
1135 my $client_answer = "";
1137 ##################################################
1138 # fetch first unprocessed message from incoming_db
1139 # sometimes the program is faster than sqlite, so wait until informations are present at db
1140 my $id_sql;
1141 my $id_res;
1142 my $message_id;
1143 # TODO : das hier ist sehr sehr unschön
1144 # to be tested: speed enhancement with usleep 100000???
1145 while (1) {
1146 $id_sql = "SELECT min(id) FROM $incoming_tn WHERE (NOT headertag LIKE 'answer%')";
1147 $id_res = $incoming_db->exec_statement($id_sql);
1148 $message_id = @{@$id_res[0]}[0];
1149 if (defined $message_id) { last }
1150 usleep(100000);
1151 }
1153 # fetch new message from incoming_db
1154 my $sql = "SELECT * FROM $incoming_tn WHERE id=$message_id";
1155 my $res = $incoming_db->exec_statement($sql);
1157 # prepare all variables needed to process message
1158 my $msg = @{@$res[0]}[4];
1159 my $incoming_id = @{@$res[0]}[0];
1160 my $module = @{@$res[0]}[5];
1161 my $header = @{@$res[0]}[2];
1162 my $msg_hash = $xml->XMLin($msg, ForceArray=>1);
1164 # messages which are an answer to a still running process should not be processed here
1165 if ($header =~ /^answer_(\d+)/) {
1166 return;
1167 }
1169 # delete message from db
1170 my $delete_sql = "DELETE FROM $incoming_tn WHERE id=$incoming_id";
1171 my $delete_res = $incoming_db->exec_statement($delete_sql);
1173 ######################
1174 # process incoming msg
1175 if( $error == 0) {
1176 daemon_log("$session_id INFO: Incoming msg (session_id=$session_id) with header '".@{$msg_hash->{'header'}}[0].
1177 "' from '".$heap->{'remote_ip'}."'", 5);
1178 daemon_log("$session_id DEBUG: Processing module ".$module, 7);
1179 $answer_l = &{ $module."::process_incoming_msg" }($msg, $msg_hash, $session_id);
1181 if ( 0 < @{$answer_l} ) {
1182 my $answer_str = join("\n", @{$answer_l});
1183 while ($answer_str =~ /<header>(\w+)<\/header>/g) {
1184 daemon_log("$session_id INFO: got answer message with header '$1'", 5);
1185 }
1186 daemon_log("$session_id DEBUG: $module: got answer from module: \n".$answer_str,8);
1187 } else {
1188 daemon_log("$session_id DEBUG: $module: got no answer from module!" ,8);
1189 }
1191 }
1192 if( !$answer_l ) { $error++ };
1194 ########
1195 # answer
1196 if( $error == 0 ) {
1198 foreach my $answer ( @{$answer_l} ) {
1199 # check outgoing msg to xml validity
1200 my $answer_hash = &check_outgoing_xml_validity($answer);
1201 if( not defined $answer_hash ) { next; }
1203 $answer_header = @{$answer_hash->{'header'}}[0];
1204 @answer_target_l = @{$answer_hash->{'target'}};
1205 $answer_source = @{$answer_hash->{'source'}}[0];
1207 # deliver msg to all targets
1208 foreach my $answer_target ( @answer_target_l ) {
1210 # targets of msg are all gosa-si-clients in known_clients_db
1211 if( $answer_target eq "*" ) {
1212 # answer is for all clients
1213 my $sql_statement= "SELECT * FROM known_clients";
1214 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1215 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1216 my $host_name = $hit->{hostname};
1217 my $host_key = $hit->{hostkey};
1218 my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1219 &update_jobdb_status_for_send_msgs($answer, $error);
1220 }
1221 }
1223 # targets of msg are all gosa-si-server in known_server_db
1224 elsif( $answer_target eq "KNOWN_SERVER" ) {
1225 # answer is for all server in known_server
1226 my $sql_statement= "SELECT * FROM known_server";
1227 my $query_res = $known_server_db->select_dbentry( $sql_statement );
1228 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1229 my $host_name = $hit->{hostname};
1230 my $host_key = $hit->{hostkey};
1231 $answer =~ s/KNOWN_SERVER/$host_name/g;
1232 my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1233 &update_jobdb_status_for_send_msgs($answer, $error);
1234 }
1235 }
1237 # target of msg is GOsa
1238 elsif( $answer_target eq "GOSA" ) {
1239 my $session_id = ($1) if $answer =~ /<session_id>(\d+?)<\/session_id>/;
1240 my $add_on = "";
1241 if( defined $session_id ) {
1242 $add_on = ".session_id=$session_id";
1243 }
1244 # answer is for GOSA and has to returned to connected client
1245 my $gosa_answer = &encrypt_msg($answer, $GosaPackages_key);
1246 $client_answer = $gosa_answer.$add_on;
1247 }
1249 # target of msg is job queue at this host
1250 elsif( $answer_target eq "JOBDB") {
1251 $answer =~ /<header>(\S+)<\/header>/;
1252 my $header;
1253 if( defined $1 ) { $header = $1; }
1254 my $error = &send_msg_to_target($answer, $server_address, $GosaPackages_key, $header, $session_id);
1255 &update_jobdb_status_for_send_msgs($answer, $error);
1256 }
1258 # target of msg is a mac address
1259 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 ) {
1260 daemon_log("$session_id INFO: target is mac address '$answer_target', looking for host in known_clients", 5);
1261 my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$answer_target'";
1262 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1263 my $found_ip_flag = 0;
1264 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1265 my $host_name = $hit->{hostname};
1266 my $host_key = $hit->{hostkey};
1267 $answer =~ s/$answer_target/$host_name/g;
1268 daemon_log("$session_id INFO: found host '$host_name', associated to '$answer_target'", 5);
1269 my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1270 &update_jobdb_status_for_send_msgs($answer, $error);
1271 $found_ip_flag++ ;
1272 }
1273 if( $found_ip_flag == 0) {
1274 daemon_log("$session_id WARNING: no host found in known_clients with mac address '$answer_target'", 3);
1275 if( $bus_activ eq "true" ) {
1276 daemon_log("$session_id INFO: try to forward msg '$answer_header' to bus '$bus_address'", 5);
1277 my $sql_statement = "SELECT * FROM known_server WHERE hostname='$bus_address'";
1278 my $query_res = $known_server_db->select_dbentry( $sql_statement );
1279 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1280 my $bus_address = $hit->{hostname};
1281 my $bus_key = $hit->{hostkey};
1282 my $error = &send_msg_to_target($answer, $bus_address, $bus_key, $answer_header, $session_id);
1283 &update_jobdb_status_for_send_msgs($answer, $error);
1284 last;
1285 }
1286 }
1288 }
1290 # answer is for one specific host
1291 } else {
1292 # get encrypt_key
1293 my $encrypt_key = &get_encrypt_key($answer_target);
1294 if( not defined $encrypt_key ) {
1295 # unknown target, forward msg to bus
1296 daemon_log("$session_id WARNING: unknown target '$answer_target'", 3);
1297 if( $bus_activ eq "true" ) {
1298 daemon_log("$session_id INFO: try to forward msg '$answer_header' to bus '$bus_address'", 5);
1299 my $sql_statement = "SELECT * FROM known_server WHERE hostname='$bus_address'";
1300 my $query_res = $known_server_db->select_dbentry( $sql_statement );
1301 my $res_length = keys( %{$query_res} );
1302 if( $res_length == 0 ){
1303 daemon_log("$session_id WARNING: send '$answer_header' to '$bus_address' failed, ".
1304 "no bus found in known_server", 3);
1305 }
1306 else {
1307 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1308 my $bus_key = $hit->{hostkey};
1309 my $error = &send_msg_to_target($answer, $bus_address, $bus_key, $answer_header,$session_id );
1310 &update_jobdb_status_for_send_msgs($answer, $error);
1311 }
1312 }
1313 }
1314 next;
1315 }
1316 my $error = &send_msg_to_target($answer, $answer_target, $encrypt_key, $answer_header,$session_id);
1317 &update_jobdb_status_for_send_msgs($answer, $error);
1318 }
1319 }
1320 }
1321 }
1323 my $filter = POE::Filter::Reference->new();
1324 my %result = (
1325 status => "seems ok to me",
1326 answer => $client_answer,
1327 );
1329 my $output = $filter->put( [ \%result ] );
1330 print @$output;
1333 }
1336 sub trigger_db_loop {
1337 my ($kernel) = @_ ;
1338 $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1339 $kernel->delay_set('watch_for_done_jobs', $job_queue_loop_delay);
1340 $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay);
1341 $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay);
1342 $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay);
1343 $kernel->delay_set('watch_for_old_known_clients', $job_queue_loop_delay);
1344 }
1347 sub watch_for_done_jobs {
1348 my ($kernel,$heap) = @_[KERNEL, HEAP];
1350 my $sql_statement = "SELECT * FROM ".$job_queue_tn.
1351 " WHERE status='done'";
1352 my $res = $job_db->select_dbentry( $sql_statement );
1354 while( my ($id, $hit) = each %{$res} ) {
1355 my $jobdb_id = $hit->{id};
1356 my $sql_statement = "DELETE FROM $job_queue_tn WHERE id=$jobdb_id";
1357 my $res = $job_db->del_dbentry($sql_statement);
1358 }
1360 $kernel->delay_set('watch_for_done_jobs',$job_queue_loop_delay);
1361 }
1364 sub watch_for_new_jobs {
1365 if($watch_for_new_jobs_in_progress == 0) {
1366 $watch_for_new_jobs_in_progress = 1;
1367 my ($kernel,$heap) = @_[KERNEL, HEAP];
1369 # check gosa job queue for jobs with executable timestamp
1370 my $timestamp = &get_time();
1371 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE status='waiting' AND (CAST (timestamp AS INTEGER)) < $timestamp ORDER BY timestamp";
1372 my $res = $job_db->exec_statement( $sql_statement );
1374 # Merge all new jobs that would do the same actions
1375 my @drops;
1376 my $hits;
1377 foreach my $hit (reverse @{$res} ) {
1378 my $macaddress= lc @{$hit}[8];
1379 my $headertag= @{$hit}[5];
1380 if(
1381 defined($hits->{$macaddress}) &&
1382 defined($hits->{$macaddress}->{$headertag}) &&
1383 defined($hits->{$macaddress}->{$headertag}[0])
1384 ) {
1385 push @drops, "DELETE FROM $job_queue_tn WHERE id = $hits->{$macaddress}->{$headertag}[0]";
1386 }
1387 $hits->{$macaddress}->{$headertag}= $hit;
1388 }
1390 # Delete new jobs with a matching job in state 'processing'
1391 foreach my $macaddress (keys %{$hits}) {
1392 foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1393 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1394 if(defined($jobdb_id)) {
1395 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND headertag='$jobdb_headertag' AND status='processing'";
1396 my $res = $job_db->exec_statement( $sql_statement );
1397 foreach my $hit (@{$res}) {
1398 push @drops, "DELETE FROM $job_queue_tn WHERE id=$jobdb_id";
1399 }
1400 } else {
1401 daemon_log("J ERROR: Job without id exists for macaddress $macaddress!", 1);
1402 }
1403 }
1404 }
1406 # Commit deletion
1407 $job_db->exec_statementlist(\@drops);
1409 # Look for new jobs that could be executed
1410 foreach my $macaddress (keys %{$hits}) {
1412 # Look if there is an executing job
1413 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND status='processing'";
1414 my $res = $job_db->exec_statement( $sql_statement );
1416 # Skip new jobs for host if there is a processing job
1417 if(defined($res) and defined @{$res}[0]) {
1418 next;
1419 }
1421 foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1422 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1423 if(defined($jobdb_id)) {
1424 my $job_msg = @{$hits->{$macaddress}->{$jobdb_headertag}}[7];
1426 daemon_log("J DEBUG: its time to execute $job_msg", 7);
1427 my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$macaddress'";
1428 my $res_hash = $known_clients_db->select_dbentry( $sql_statement );
1430 # expect macaddress is unique!!!!!!
1431 my $target = $res_hash->{1}->{hostname};
1433 # change header
1434 $job_msg =~ s/<header>job_/<header>gosa_/;
1436 # add sqlite_id
1437 $job_msg =~ s/<\/xml>$/<jobdb_id>$jobdb_id<\/jobdb_id><\/xml>/;
1439 $job_msg =~ /<header>(\S+)<\/header>/;
1440 my $header = $1 ;
1441 my $func_error = &send_msg_to_target($job_msg, $server_address, $GosaPackages_key, $header, "J");
1443 # update status in job queue to 'processing'
1444 $sql_statement = "UPDATE $job_queue_tn SET status='processing' WHERE id=$jobdb_id";
1445 my $res = $job_db->update_dbentry($sql_statement);
1446 # TODO: abfangen ob alles in ordnung ist oder nicht, wenn nicht error schmeißen
1448 # We don't want parallel processing
1449 last;
1450 }
1451 }
1452 }
1454 $watch_for_new_jobs_in_progress = 0;
1455 $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1456 }
1457 }
1460 sub watch_for_new_messages {
1461 my ($kernel,$heap) = @_[KERNEL, HEAP];
1462 my @coll_user_msg; # collection list of outgoing messages
1464 # check messaging_db for new incoming messages with executable timestamp
1465 my $timestamp = &get_time();
1466 my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( (CAST(timestamp AS INTEGER))<$timestamp AND flag='n' AND direction='in' )";
1467 my $res = $messaging_db->exec_statement( $sql_statement );
1468 foreach my $hit (@{$res}) {
1470 # create outgoing messages
1471 my $message_to = @{$hit}[3];
1472 # translate message_to to plain login name
1473 my @message_to_l = split(/,/, $message_to);
1474 my %receiver_h;
1475 foreach my $receiver (@message_to_l) {
1476 if ($receiver =~ /^u_([\s\S]*)$/) {
1477 $receiver_h{$1} = 0;
1478 } elsif ($receiver =~ /^g_([\s\S]*)$/) {
1479 my $group_name = $1;
1480 # fetch all group members from ldap and add them to receiver hash
1481 my $ldap_handle = &get_ldap_handle();
1482 if (defined $ldap_handle) {
1483 my $mesg = $ldap_handle->search(
1484 base => $ldap_base,
1485 scope => 'sub',
1486 attrs => ['memberUid'],
1487 filter => "cn=$group_name",
1488 );
1489 if ($mesg->count) {
1490 my @entries = $mesg->entries;
1491 foreach my $entry (@entries) {
1492 my @receivers= $entry->get_value("memberUid");
1493 foreach my $receiver (@receivers) {
1494 $receiver_h{$1} = 0;
1495 }
1496 }
1497 }
1498 # translating errors ?
1499 if ($mesg->code) {
1500 daemon_log("M ERROR: unable to translate group '$group_name' to user list for message delivery: $mesg->error", 1);
1501 }
1502 # ldap handle error ?
1503 } else {
1504 daemon_log("M ERROR: unable to translate group '$group_name' to user list for message delivery: no ldap handle available", 1);
1505 }
1506 } else {
1507 my $sbjct = &encode_base64(@{$hit}[1]);
1508 my $msg = &encode_base64(@{$hit}[7]);
1509 &daemon_log("M WARNING: unknown receiver '$receiver' for a user-message '$sbjct - $msg'", 3);
1510 }
1511 }
1512 my @receiver_l = keys(%receiver_h);
1514 my $message_id = @{$hit}[0];
1516 #add each outgoing msg to messaging_db
1517 my $receiver;
1518 foreach $receiver (@receiver_l) {
1519 my $sql_statement = "INSERT INTO $messaging_tn (id, subject, message_from, message_to, flag, direction, delivery_time, message, timestamp) ".
1520 "VALUES ('".
1521 $message_id."', '". # id
1522 @{$hit}[1]."', '". # subject
1523 @{$hit}[2]."', '". # message_from
1524 $receiver."', '". # message_to
1525 "none"."', '". # flag
1526 "out"."', '". # direction
1527 @{$hit}[6]."', '". # delivery_time
1528 @{$hit}[7]."', '". # message
1529 $timestamp."'". # timestamp
1530 ")";
1531 &daemon_log("M DEBUG: $sql_statement", 1);
1532 my $res = $messaging_db->exec_statement($sql_statement);
1533 &daemon_log("M INFO: message '".@{$hit}[0]."' is prepared for delivery to receiver '$receiver'", 5);
1534 }
1536 # set incoming message to flag d=deliverd
1537 $sql_statement = "UPDATE $messaging_tn SET flag='p' WHERE id='$message_id'";
1538 &daemon_log("M DEBUG: $sql_statement", 7);
1539 $res = $messaging_db->update_dbentry($sql_statement);
1540 &daemon_log("M INFO: message '$message_id' is set to flag 'p' (processed)", 5);
1541 }
1543 $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay);
1544 return;
1545 }
1547 sub watch_for_delivery_messages {
1548 my ($kernel, $heap) = @_[KERNEL, HEAP];
1550 # select outgoing messages
1551 my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( flag='p' AND direction='out' )";
1552 #&daemon_log("0 DEBUG: $sql", 7);
1553 my $res = $messaging_db->exec_statement( $sql_statement );
1555 # build out msg for each usr
1556 foreach my $hit (@{$res}) {
1557 my $receiver = @{$hit}[3];
1558 my $msg_id = @{$hit}[0];
1559 my $subject = @{$hit}[1];
1560 my $message = @{$hit}[7];
1562 # resolve usr -> host where usr is logged in
1563 my $sql = "SELECT * FROM $login_users_tn WHERE (user='$receiver')";
1564 #&daemon_log("0 DEBUG: $sql", 7);
1565 my $res = $login_users_db->exec_statement($sql);
1567 # reciver is logged in nowhere
1568 if (not ref(@$res[0]) eq "ARRAY") { next; }
1570 my $send_succeed = 0;
1571 foreach my $hit (@$res) {
1572 my $receiver_host = @$hit[0];
1573 &daemon_log("M DEBUG: user '$receiver' is logged in at host '$receiver_host'", 7);
1575 # fetch key to encrypt msg propperly for usr/host
1576 my $sql = "SELECT * FROM $known_clients_tn WHERE (hostname='$receiver_host')";
1577 &daemon_log("0 DEBUG: $sql", 7);
1578 my $res = $known_clients_db->select_dbentry($sql);
1580 # host is already down
1581 if (not ref(@$res[0]) eq "ARRAY") { next; }
1583 # host is on
1584 my $receiver_key = @{@{$res}[0]}[2];
1585 my %data = ('subject' => $subject, 'message' => $message, 'usr' => $receiver);
1586 my $out_msg = &build_msg("usr_msg", $server_address, $receiver_host, \%data );
1587 my $error = &send_msg_to_target($out_msg, $receiver_host, $receiver_key, "usr_msg", 0);
1588 if ($error == 0 ) {
1589 $send_succeed++ ;
1590 }
1591 }
1593 if ($send_succeed) {
1594 # set outgoing msg at db to deliverd
1595 my $sql = "UPDATE $messaging_tn SET flag='d' WHERE (id='$msg_id' AND direction='out' AND message_to='$receiver')";
1596 &daemon_log("0 DEBUG: $sql", 7);
1597 my $res = $messaging_db->exec_statement($sql);
1598 }
1599 }
1601 $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay);
1602 return;
1603 }
1606 sub watch_for_done_messages {
1607 my ($kernel,$heap) = @_[KERNEL, HEAP];
1609 my $sql = "SELECT * FROM $messaging_tn WHERE (flag='p' AND direction='in')";
1610 #&daemon_log("0 DEBUG: $sql", 7);
1611 my $res = $messaging_db->exec_statement($sql);
1613 foreach my $hit (@{$res}) {
1614 my $msg_id = @{$hit}[0];
1616 my $sql = "SELECT * FROM $messaging_tn WHERE (id='$msg_id' AND direction='out' AND (NOT flag='s'))";
1617 #&daemon_log("0 DEBUG: $sql", 7);
1618 my $res = $messaging_db->exec_statement($sql);
1620 # not all usr msgs have been seen till now
1621 if ( ref(@$res[0]) eq "ARRAY") { next; }
1623 $sql = "DELETE FROM $messaging_tn WHERE (id='$msg_id')";
1624 #&daemon_log("0 DEBUG: $sql", 7);
1625 $res = $messaging_db->exec_statement($sql);
1627 }
1629 $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay);
1630 return;
1631 }
1634 sub watch_for_old_known_clients {
1635 my ($kernel,$heap) = @_[KERNEL, HEAP];
1637 my $sql_statement = "SELECT * FROM $known_clients_tn";
1638 my $res = $known_clients_db->select_dbentry( $sql_statement );
1640 my $act_time = int(&get_time());
1641 while ( my ($hit_num, $hit) = each %$res) {
1642 my $expired_timestamp = int($hit->{'timestamp'}) + (2 * int($hit->{'keylifetime'}));
1643 if ($act_time > $expired_timestamp) {
1644 my $hostname = $hit->{'hostname'};
1645 my $del_sql = "DELETE FROM $known_clients_tn WHERE hostname='$hostname'";
1646 my $del_res = $known_clients_db->exec_statement($del_sql);
1648 &main::daemon_log("0 INFO: timestamp of client '$hostname' is expired, client will be deleted from known_clients_db", 5);
1649 }
1651 }
1653 $kernel->delay_set('watch_for_old_known_clients', $job_queue_loop_delay);
1654 }
1657 sub get_ldap_handle {
1658 my ($session_id) = @_;
1659 my $heap;
1660 my $ldap_handle;
1662 if (not defined $session_id ) { $session_id = 0 };
1663 if ($session_id =~ /[^0-9]*/) { $session_id = 0 };
1665 if ($session_id == 0) {
1666 daemon_log("$session_id DEBUG: get_ldap_handle invoked without a session_id, create a new ldap_handle", 7);
1667 $ldap_handle = Net::LDAP->new( $ldap_uri );
1668 $ldap_handle->bind($ldap_admin_dn, apassword => $ldap_admin_password);
1670 } else {
1671 my $session_reference = $global_kernel->ID_id_to_session($session_id);
1672 if( defined $session_reference ) {
1673 $heap = $session_reference->get_heap();
1674 }
1676 if (not defined $heap) {
1677 daemon_log("$session_id DEBUG: cannot get heap for session_id '$session_id'", 7);
1678 return;
1679 }
1681 # TODO: This "if" is nonsense, because it doesn't prove that the
1682 # used handle is still valid - or if we've to reconnect...
1683 #if (not exists $heap->{ldap_handle}) {
1684 $ldap_handle = Net::LDAP->new( $ldap_uri );
1685 $ldap_handle->bind($ldap_admin_dn, password => $ldap_admin_password);
1686 $heap->{ldap_handle} = $ldap_handle;
1687 #}
1688 }
1689 return $ldap_handle;
1690 }
1693 sub change_fai_state {
1694 my ($st, $targets, $session_id) = @_;
1695 $session_id = 0 if not defined $session_id;
1696 # Set FAI state to localboot
1697 my %mapActions= (
1698 reboot => '',
1699 update => 'softupdate',
1700 localboot => 'localboot',
1701 reinstall => 'install',
1702 rescan => '',
1703 wake => '',
1704 memcheck => 'memcheck',
1705 sysinfo => 'sysinfo',
1706 install => 'install',
1707 );
1709 # Return if this is unknown
1710 if (!exists $mapActions{ $st }){
1711 daemon_log("$session_id ERROR: unknown action '$st', can not translate ot FAIstate", 1);
1712 return;
1713 }
1715 my $state= $mapActions{ $st };
1717 my $ldap_handle = &get_ldap_handle($session_id);
1718 if( defined($ldap_handle) ) {
1720 # Build search filter for hosts
1721 my $search= "(&(objectClass=GOhard)";
1722 foreach (@{$targets}){
1723 $search.= "(macAddress=$_)";
1724 }
1725 $search.= ")";
1727 # If there's any host inside of the search string, procress them
1728 if (!($search =~ /macAddress/)){
1729 daemon_log("$session_id ERROR: no macAddress found in filter statement for LDAP search: '$search'", 1);
1730 return;
1731 }
1733 # Perform search for Unit Tag
1734 my $mesg = $ldap_handle->search(
1735 base => $ldap_base,
1736 scope => 'sub',
1737 attrs => ['dn', 'FAIstate', 'objectClass'],
1738 filter => "$search"
1739 );
1741 if ($mesg->count) {
1742 my @entries = $mesg->entries;
1743 foreach my $entry (@entries) {
1744 # Only modify entry if it is not set to '$state'
1745 if ($entry->get_value("FAIstate") ne "$state"){
1746 daemon_log("$session_id INFO: Setting FAIstate to '$state' for ".$entry->dn, 5);
1747 my $result;
1748 my %tmp = map { $_ => 1 } $entry->get_value("objectClass");
1749 if (exists $tmp{'FAIobject'}){
1750 if ($state eq ''){
1751 $result= $ldap_handle->modify($entry->dn, changes => [
1752 delete => [ FAIstate => [] ] ]);
1753 } else {
1754 $result= $ldap_handle->modify($entry->dn, changes => [
1755 replace => [ FAIstate => $state ] ]);
1756 }
1757 } elsif ($state ne ''){
1758 $result= $ldap_handle->modify($entry->dn, changes => [
1759 add => [ objectClass => 'FAIobject' ],
1760 add => [ FAIstate => $state ] ]);
1761 }
1763 # Errors?
1764 if ($result->code){
1765 daemon_log("$session_id Error: Setting FAIstate to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
1766 }
1767 } else {
1768 daemon_log("$session_id DEBUG FAIstate at host '".$entry->dn."' already at state '$st'", 7);
1769 }
1770 }
1771 }
1772 # if no ldap handle defined
1773 } else {
1774 daemon_log("$session_id ERROR: no LDAP handle defined for update FAIstate", 1);
1775 }
1777 }
1780 sub change_goto_state {
1781 my ($st, $targets, $session_id) = @_;
1782 $session_id = 0 if not defined $session_id;
1784 # Switch on or off?
1785 my $state= $st eq 'active' ? 'active': 'locked';
1787 my $ldap_handle = &get_ldap_handle($session_id);
1788 if( defined($ldap_handle) ) {
1790 # Build search filter for hosts
1791 my $search= "(&(objectClass=GOhard)";
1792 foreach (@{$targets}){
1793 $search.= "(macAddress=$_)";
1794 }
1795 $search.= ")";
1797 # If there's any host inside of the search string, procress them
1798 if (!($search =~ /macAddress/)){
1799 return;
1800 }
1802 # Perform search for Unit Tag
1803 my $mesg = $ldap_handle->search(
1804 base => $ldap_base,
1805 scope => 'sub',
1806 attrs => ['dn', 'gotoMode'],
1807 filter => "$search"
1808 );
1810 if ($mesg->count) {
1811 my @entries = $mesg->entries;
1812 foreach my $entry (@entries) {
1814 # Only modify entry if it is not set to '$state'
1815 if ($entry->get_value("gotoMode") ne $state){
1817 daemon_log("$session_id INFO: Setting gotoMode to '$state' for ".$entry->dn, 5);
1818 my $result;
1819 $result= $ldap_handle->modify($entry->dn, changes => [
1820 replace => [ gotoMode => $state ] ]);
1822 # Errors?
1823 if ($result->code){
1824 &daemon_log("$session_id Error: Setting gotoMode to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
1825 }
1827 }
1828 }
1829 }
1831 }
1832 }
1835 sub run_create_fai_server_db {
1836 my ($kernel, $session, $heap, $table_name) = @_[KERNEL, SESSION, HEAP, ARG0];
1837 my $session_id = $session->ID;
1838 my $task = POE::Wheel::Run->new(
1839 Program => sub { &create_fai_server_db($table_name,$kernel, undef, $session_id) },
1840 StdoutEvent => "session_run_result",
1841 StderrEvent => "session_run_debug",
1842 CloseEvent => "session_run_done",
1843 );
1845 $heap->{task}->{ $task->ID } = $task;
1846 return;
1847 }
1850 sub create_fai_server_db {
1851 my ($table_name, $kernel, $dont_create_packages_list, $session_id) = @_;
1852 my $result;
1854 if (not defined $session_id) { $session_id = 0; }
1855 my $ldap_handle = &get_ldap_handle();
1856 if(defined($ldap_handle)) {
1857 daemon_log("$session_id INFO: create_fai_server_db: start", 5);
1858 my $mesg= $ldap_handle->search(
1859 base => $ldap_base,
1860 scope => 'sub',
1861 attrs => ['FAIrepository', 'gosaUnitTag'],
1862 filter => "(&(FAIrepository=*)(objectClass=FAIrepositoryServer))",
1863 );
1864 if($mesg->{'resultCode'} == 0 &&
1865 $mesg->count != 0) {
1866 foreach my $entry (@{$mesg->{entries}}) {
1867 if($entry->exists('FAIrepository')) {
1868 # Add an entry for each Repository configured for server
1869 foreach my $repo(@{$entry->get_value('FAIrepository', asref => 1)}) {
1870 my($tmp_url,$tmp_server,$tmp_release,$tmp_sections) = split(/\|/, $repo);
1871 my $tmp_tag= $entry->get_value('gosaUnitTag') || "";
1872 $result= $fai_server_db->add_dbentry( {
1873 table => $table_name,
1874 primkey => ['server', 'release', 'tag'],
1875 server => $tmp_url,
1876 release => $tmp_release,
1877 sections => $tmp_sections,
1878 tag => (length($tmp_tag)>0)?$tmp_tag:"",
1879 } );
1880 }
1881 }
1882 }
1883 }
1884 daemon_log("$session_id INFO: create_fai_server_db: finished", 5);
1886 # TODO: Find a way to post the 'create_packages_list_db' event
1887 if(not defined($dont_create_packages_list)) {
1888 &create_packages_list_db(undef, undef, $session_id);
1889 }
1890 }
1892 $ldap_handle->disconnect;
1893 return $result;
1894 }
1897 sub run_create_fai_release_db {
1898 my ($session, $heap, $table_name) = @_[SESSION, HEAP, ARG0];
1899 my $session_id = $session->ID;
1900 my $task = POE::Wheel::Run->new(
1901 Program => sub { &create_fai_release_db($table_name, $session_id) },
1902 StdoutEvent => "session_run_result",
1903 StderrEvent => "session_run_debug",
1904 CloseEvent => "session_run_done",
1905 );
1907 $heap->{task}->{ $task->ID } = $task;
1908 return;
1909 }
1912 sub create_fai_release_db {
1913 my ($table_name, $session_id) = @_;
1914 my $result;
1916 # used for logging
1917 if (not defined $session_id) { $session_id = 0; }
1919 my $ldap_handle = &get_ldap_handle();
1920 if(defined($ldap_handle)) {
1921 daemon_log("$session_id INFO: create_fai_release_db: start",5);
1922 my $mesg= $ldap_handle->search(
1923 base => $ldap_base,
1924 scope => 'sub',
1925 attrs => [],
1926 filter => "(&(objectClass=organizationalUnit)(ou=fai))",
1927 );
1928 if($mesg->{'resultCode'} == 0 &&
1929 $mesg->count != 0) {
1930 # Walk through all possible FAI container ou's
1931 my @sql_list;
1932 my $timestamp= &get_time();
1933 foreach my $ou (@{$mesg->{entries}}) {
1934 my $tmp_classes= resolve_fai_classes($ou->dn, $ldap_handle, $session_id);
1935 if(defined($tmp_classes) && ref($tmp_classes) eq 'HASH') {
1936 my @tmp_array=get_fai_release_entries($tmp_classes);
1937 if(@tmp_array) {
1938 foreach my $entry (@tmp_array) {
1939 if(defined($entry) && ref($entry) eq 'HASH') {
1940 my $sql=
1941 "INSERT INTO $table_name "
1942 ."(timestamp, release, class, type, state) VALUES ("
1943 .$timestamp.","
1944 ."'".$entry->{'release'}."',"
1945 ."'".$entry->{'class'}."',"
1946 ."'".$entry->{'type'}."',"
1947 ."'".$entry->{'state'}."')";
1948 push @sql_list, $sql;
1949 }
1950 }
1951 }
1952 }
1953 }
1955 daemon_log("$session_id DEBUG: Inserting ".scalar @sql_list." entries to DB",8);
1956 if(@sql_list) {
1957 unshift @sql_list, "VACUUM";
1958 unshift @sql_list, "DELETE FROM $table_name";
1959 $fai_release_db->exec_statementlist(\@sql_list);
1960 }
1961 daemon_log("$session_id DEBUG: Done with inserting",7);
1962 }
1963 daemon_log("$session_id INFO: create_fai_release_db: finished",5);
1964 }
1965 $ldap_handle->disconnect;
1966 return $result;
1967 }
1969 sub get_fai_types {
1970 my $tmp_classes = shift || return undef;
1971 my @result;
1973 foreach my $type(keys %{$tmp_classes}) {
1974 if(defined($tmp_classes->{$type}[0]) && (!($tmp_classes->{$type}[0] =~ /^.*?removed.*?$/))) {
1975 my $entry = {
1976 type => $type,
1977 state => $tmp_classes->{$type}[0],
1978 };
1979 push @result, $entry;
1980 }
1981 }
1983 return @result;
1984 }
1986 sub get_fai_state {
1987 my $result = "";
1988 my $tmp_classes = shift || return $result;
1990 foreach my $type(keys %{$tmp_classes}) {
1991 if(defined($tmp_classes->{$type}[0])) {
1992 $result = $tmp_classes->{$type}[0];
1994 # State is equal for all types in class
1995 last;
1996 }
1997 }
1999 return $result;
2000 }
2002 sub resolve_fai_classes {
2003 my ($fai_base, $ldap_handle, $session_id) = @_;
2004 if (not defined $session_id) { $session_id = 0; }
2005 my $result;
2006 my @possible_fai_classes= ("FAIscript", "FAIhook", "FAIpartitionTable", "FAItemplate", "FAIvariable", "FAIprofile", "FAIpackageList");
2007 my $fai_filter= "(|(&(objectClass=FAIclass)(|(objectClass=".join(")(objectClass=", @possible_fai_classes).")))(objectClass=FAIbranch))";
2008 my $fai_classes;
2010 daemon_log("$session_id DEBUG: Searching for FAI entries in base $fai_base",7);
2011 my $mesg= $ldap_handle->search(
2012 base => $fai_base,
2013 scope => 'sub',
2014 attrs => ['cn','objectClass','FAIstate'],
2015 filter => $fai_filter,
2016 );
2017 daemon_log("$session_id DEBUG: Found ".$mesg->count()." FAI entries",7);
2019 if($mesg->{'resultCode'} == 0 &&
2020 $mesg->count != 0) {
2021 foreach my $entry (@{$mesg->{entries}}) {
2022 if($entry->exists('cn')) {
2023 my $tmp_dn= $entry->dn();
2025 # Skip classname and ou dn parts for class
2026 my $tmp_release = ($1) if $tmp_dn =~ /^[^,]+,[^,]+,(.*?),$fai_base$/;
2028 # Skip classes without releases
2029 if((!defined($tmp_release)) || length($tmp_release)==0) {
2030 next;
2031 }
2033 my $tmp_cn= $entry->get_value('cn');
2034 my $tmp_state= $entry->get_value('FAIstate');
2036 my $tmp_type;
2037 # Get FAI type
2038 for my $oclass(@{$entry->get_value('objectClass', asref => 1)}) {
2039 if(grep $_ eq $oclass, @possible_fai_classes) {
2040 $tmp_type= $oclass;
2041 last;
2042 }
2043 }
2045 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
2046 # A Subrelease
2047 my @sub_releases = split(/,/, $tmp_release);
2049 # Walk through subreleases and build hash tree
2050 my $hash;
2051 while(my $tmp_sub_release = pop @sub_releases) {
2052 $hash .= "\{'$tmp_sub_release'\}->";
2053 }
2054 eval('push @{$fai_classes->'.$hash.'{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";');
2055 } else {
2056 # A branch, no subrelease
2057 push @{$fai_classes->{$tmp_release}->{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";
2058 }
2059 } elsif (!$entry->exists('cn')) {
2060 my $tmp_dn= $entry->dn();
2061 my $tmp_release = ($1) if $tmp_dn =~ /^(.*?),$fai_base$/;
2063 # Skip classes without releases
2064 if((!defined($tmp_release)) || length($tmp_release)==0) {
2065 next;
2066 }
2068 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
2069 # A Subrelease
2070 my @sub_releases= split(/,/, $tmp_release);
2072 # Walk through subreleases and build hash tree
2073 my $hash;
2074 while(my $tmp_sub_release = pop @sub_releases) {
2075 $hash .= "\{'$tmp_sub_release'\}->";
2076 }
2077 # Remove the last two characters
2078 chop($hash);
2079 chop($hash);
2081 eval('$fai_classes->'.$hash.'= {}');
2082 } else {
2083 # A branch, no subrelease
2084 if(!exists($fai_classes->{$tmp_release})) {
2085 $fai_classes->{$tmp_release} = {};
2086 }
2087 }
2088 }
2089 }
2091 # The hash is complete, now we can honor the copy-on-write based missing entries
2092 foreach my $release (keys %$fai_classes) {
2093 $result->{$release}= deep_copy(apply_fai_inheritance($fai_classes->{$release}));
2094 }
2095 }
2096 return $result;
2097 }
2099 sub apply_fai_inheritance {
2100 my $fai_classes = shift || return {};
2101 my $tmp_classes;
2103 # Get the classes from the branch
2104 foreach my $class (keys %{$fai_classes}) {
2105 # Skip subreleases
2106 if($class =~ /^ou=.*$/) {
2107 next;
2108 } else {
2109 $tmp_classes->{$class}= deep_copy($fai_classes->{$class});
2110 }
2111 }
2113 # Apply to each subrelease
2114 foreach my $subrelease (keys %{$fai_classes}) {
2115 if($subrelease =~ /ou=/) {
2116 foreach my $tmp_class (keys %{$tmp_classes}) {
2117 if(!exists($fai_classes->{$subrelease}->{$tmp_class})) {
2118 $fai_classes->{$subrelease}->{$tmp_class} =
2119 deep_copy($tmp_classes->{$tmp_class});
2120 } else {
2121 foreach my $type (keys %{$tmp_classes->{$tmp_class}}) {
2122 if(!exists($fai_classes->{$subrelease}->{$tmp_class}->{$type})) {
2123 $fai_classes->{$subrelease}->{$tmp_class}->{$type}=
2124 deep_copy($tmp_classes->{$tmp_class}->{$type});
2125 }
2126 }
2127 }
2128 }
2129 }
2130 }
2132 # Find subreleases in deeper levels
2133 foreach my $subrelease (keys %{$fai_classes}) {
2134 if($subrelease =~ /ou=/) {
2135 foreach my $subsubrelease (keys %{$fai_classes->{$subrelease}}) {
2136 if($subsubrelease =~ /ou=/) {
2137 apply_fai_inheritance($fai_classes->{$subrelease});
2138 }
2139 }
2140 }
2141 }
2143 return $fai_classes;
2144 }
2146 sub get_fai_release_entries {
2147 my $tmp_classes = shift || return;
2148 my $parent = shift || "";
2149 my @result = shift || ();
2151 foreach my $entry (keys %{$tmp_classes}) {
2152 if(defined($entry)) {
2153 if($entry =~ /^ou=.*$/) {
2154 my $release_name = $entry;
2155 $release_name =~ s/ou=//g;
2156 if(length($parent)>0) {
2157 $release_name = $parent."/".$release_name;
2158 }
2159 my @bufentries = get_fai_release_entries($tmp_classes->{$entry}, $release_name, @result);
2160 foreach my $bufentry(@bufentries) {
2161 push @result, $bufentry;
2162 }
2163 } else {
2164 my @types = get_fai_types($tmp_classes->{$entry});
2165 foreach my $type (@types) {
2166 push @result,
2167 {
2168 'class' => $entry,
2169 'type' => $type->{'type'},
2170 'release' => $parent,
2171 'state' => $type->{'state'},
2172 };
2173 }
2174 }
2175 }
2176 }
2178 return @result;
2179 }
2181 sub deep_copy {
2182 my $this = shift;
2183 if (not ref $this) {
2184 $this;
2185 } elsif (ref $this eq "ARRAY") {
2186 [map deep_copy($_), @$this];
2187 } elsif (ref $this eq "HASH") {
2188 +{map { $_ => deep_copy($this->{$_}) } keys %$this};
2189 } else { die "what type is $_?" }
2190 }
2193 sub session_run_result {
2194 my ($kernel, $heap, $client_answer) = @_[KERNEL, HEAP, ARG0];
2195 $kernel->sig(CHLD => "child_reap");
2196 }
2198 sub session_run_debug {
2199 my $result = $_[ARG0];
2200 print STDERR "$result\n";
2201 }
2203 sub session_run_done {
2204 my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
2205 delete $heap->{task}->{$task_id};
2206 }
2209 sub create_sources_list {
2210 my $session_id = shift;
2211 my $ldap_handle = &main::get_ldap_handle;
2212 my $result="/tmp/gosa_si_tmp_sources_list";
2214 # Remove old file
2215 if(stat($result)) {
2216 unlink($result);
2217 &main::daemon_log("$session_id DEBUG: remove an old version of '$result'", 7);
2218 }
2220 my $fh;
2221 open($fh, ">$result");
2222 if (not defined $fh) {
2223 &main::daemon_log("$session_id DEBUG: cannot open '$result' for writing", 7);
2224 return undef;
2225 }
2226 if(defined($main::ldap_server_dn) and length($main::ldap_server_dn) > 0) {
2227 my $mesg=$ldap_handle->search(
2228 base => $main::ldap_server_dn,
2229 scope => 'base',
2230 attrs => 'FAIrepository',
2231 filter => 'objectClass=FAIrepositoryServer'
2232 );
2233 if($mesg->count) {
2234 foreach my $entry(@{$mesg->{'entries'}}) {
2235 foreach my $value(@{$entry->get_value('FAIrepository', asref => 1)}) {
2236 my ($server, $tag, $release, $sections)= split /\|/, $value;
2237 my $line = "deb $server $release";
2238 $sections =~ s/,/ /g;
2239 $line.= " $sections";
2240 print $fh $line."\n";
2241 }
2242 }
2243 }
2244 } else {
2245 if (defined $main::ldap_server_dn){
2246 &main::daemon_log("$session_id ERROR: something wrong with ldap_server_dn '$main::ldap_server_dn', abort create_sources_list", 1);
2247 } else {
2248 &main::daemon_log("$session_id ERROR: no ldap_server_dn found, abort create_sources_list", 1);
2249 }
2250 }
2251 close($fh);
2253 return $result;
2254 }
2257 sub run_create_packages_list_db {
2258 my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
2259 my $session_id = $session->ID;
2261 my $task = POE::Wheel::Run->new(
2262 Priority => +20,
2263 Program => sub {&create_packages_list_db(undef, undef, $session_id)},
2264 StdoutEvent => "session_run_result",
2265 StderrEvent => "session_run_debug",
2266 CloseEvent => "session_run_done",
2267 );
2268 $heap->{task}->{ $task->ID } = $task;
2269 }
2272 sub create_packages_list_db {
2273 my ($ldap_handle, $sources_file, $session_id) = @_;
2275 # it should not be possible to trigger a recreation of packages_list_db
2276 # while packages_list_db is under construction, so set flag packages_list_under_construction
2277 # which is tested befor recreation can be started
2278 if (-r $packages_list_under_construction) {
2279 daemon_log("$session_id WARNING: packages_list_db is right now under construction, please wait until this process is finished", 3);
2280 return;
2281 } else {
2282 daemon_log("$session_id INFO: create_packages_list_db: start", 5);
2283 # set packages_list_under_construction to true
2284 system("touch $packages_list_under_construction");
2285 @packages_list_statements=();
2286 }
2288 if (not defined $session_id) { $session_id = 0; }
2289 if (not defined $ldap_handle) {
2290 $ldap_handle= &get_ldap_handle();
2292 if (not defined $ldap_handle) {
2293 daemon_log("$session_id ERROR: no ldap_handle available to create_packages_list_db", 1);
2294 unlink($packages_list_under_construction);
2295 return;
2296 }
2297 }
2298 if (not defined $sources_file) {
2299 &main::daemon_log("$session_id INFO: no sources_file given for creating packages list so trigger creation of it", 5);
2300 $sources_file = &create_sources_list($session_id);
2301 }
2303 if (not defined $sources_file) {
2304 &main::daemon_log("$session_id ERROR: no sources_file given under '$sources_file', skip create_packages_list_db", 1);
2305 unlink($packages_list_under_construction);
2306 return;
2307 }
2309 my $line;
2311 open(CONFIG, "<$sources_file") or do {
2312 daemon_log( "$session_id ERROR: create_packages_list_db: Failed to open '$sources_file'", 1);
2313 unlink($packages_list_under_construction);
2314 return;
2315 };
2317 # Read lines
2318 while ($line = <CONFIG>){
2319 # Unify
2320 chop($line);
2321 $line =~ s/^\s+//;
2322 $line =~ s/^\s+/ /;
2324 # Strip comments
2325 $line =~ s/#.*$//g;
2327 # Skip empty lines
2328 if ($line =~ /^\s*$/){
2329 next;
2330 }
2332 # Interpret deb line
2333 if ($line =~ /^deb [^\s]+\s[^\s]+\s[^\s]+/){
2334 my( $baseurl, $dist, $sections ) = ($line =~ /^deb\s([^\s]+)\s+([^\s]+)\s+(.*)$/);
2335 my $section;
2336 foreach $section (split(' ', $sections)){
2337 &parse_package_info( $baseurl, $dist, $section, $session_id );
2338 }
2339 }
2340 }
2342 close (CONFIG);
2344 find(\&cleanup_and_extract, keys( %repo_dirs ));
2345 &main::strip_packages_list_statements();
2346 unshift @packages_list_statements, "VACUUM";
2347 $packages_list_db->exec_statementlist(\@packages_list_statements);
2348 unlink($packages_list_under_construction);
2349 daemon_log("$session_id INFO: create_packages_list_db: finished", 5);
2350 return;
2351 }
2353 # This function should do some intensive task to minimize the db-traffic
2354 sub strip_packages_list_statements {
2355 my @existing_entries= @{$packages_list_db->exec_statement("SELECT * FROM $main::packages_list_tn")};
2356 my @new_statement_list=();
2357 my $hash;
2358 my $insert_hash;
2359 my $update_hash;
2360 my $delete_hash;
2361 my $local_timestamp=get_time();
2363 foreach my $existing_entry (@existing_entries) {
2364 $hash->{@{$existing_entry}[0]}->{@{$existing_entry}[1]}->{@{$existing_entry}[2]}= $existing_entry;
2365 }
2367 foreach my $statement (@packages_list_statements) {
2368 if($statement =~ /^INSERT/i) {
2369 # Assign the values from the insert statement
2370 my ($distribution,$package,$version,$section,$description,$template,$timestamp) = ($1,$2,$3,$4,$5,$6,$7) if $statement =~
2371 /^INSERT\s+?INTO\s+?$main::packages_list_tn\s+?VALUES\s*?\('(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)'\s*?\)$/si;
2372 if(exists($hash->{$distribution}->{$package}->{$version})) {
2373 # If section or description has changed, update the DB
2374 if(
2375 (! (@{$hash->{$distribution}->{$package}->{$version}}[3] eq $section)) or
2376 (! (@{$hash->{$distribution}->{$package}->{$version}}[4] eq $description))
2377 ) {
2378 @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,undef);
2379 }
2380 } else {
2381 # Insert a non-existing entry to db
2382 @{$insert_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
2383 }
2384 } elsif ($statement =~ /^UPDATE/i) {
2385 my ($template,$package,$version) = ($1,$2,$3) if $statement =~
2386 /^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;
2387 foreach my $distribution (keys %{$hash}) {
2388 if(exists($insert_hash->{$distribution}->{$package}->{$version})) {
2389 # update the insertion hash to execute only one query per package (insert instead insert+update)
2390 @{$insert_hash->{$distribution}->{$package}->{$version}}[5]= $template;
2391 } elsif(exists($hash->{$distribution}->{$package}->{$version})) {
2392 if( ! (@{$hash->{$distribution}->{$package}->{$version}}[5] eq $template)) {
2393 my $section;
2394 my $description;
2395 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) and
2396 length(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) > 0 ) {
2397 $section= @{$update_hash->{$distribution}->{$package}->{$version}}[3];
2398 }
2399 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
2400 $description= @{$update_hash->{$distribution}->{$package}->{$version}}[4];
2401 }
2402 @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
2403 }
2404 }
2405 }
2406 }
2407 }
2409 # TODO: Check for orphaned entries
2411 # unroll the insert_hash
2412 foreach my $distribution (keys %{$insert_hash}) {
2413 foreach my $package (keys %{$insert_hash->{$distribution}}) {
2414 foreach my $version (keys %{$insert_hash->{$distribution}->{$package}}) {
2415 push @new_statement_list, "INSERT INTO $main::packages_list_tn VALUES ('$distribution','$package','$version',"
2416 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[3]',"
2417 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[4]',"
2418 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[5]',"
2419 ."'$local_timestamp')";
2420 }
2421 }
2422 }
2424 # unroll the update hash
2425 foreach my $distribution (keys %{$update_hash}) {
2426 foreach my $package (keys %{$update_hash->{$distribution}}) {
2427 foreach my $version (keys %{$update_hash->{$distribution}->{$package}}) {
2428 my $set = "";
2429 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3])) {
2430 $set .= "section = '@{$update_hash->{$distribution}->{$package}->{$version}}[3]', ";
2431 }
2432 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
2433 $set .= "description = '@{$update_hash->{$distribution}->{$package}->{$version}}[4]', ";
2434 }
2435 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[5])) {
2436 $set .= "template = '@{$update_hash->{$distribution}->{$package}->{$version}}[5]', ";
2437 }
2438 if(defined($set) and length($set) > 0) {
2439 $set .= "timestamp = '$local_timestamp'";
2440 } else {
2441 next;
2442 }
2443 push @new_statement_list,
2444 "UPDATE $main::packages_list_tn SET $set WHERE"
2445 ." distribution = '$distribution'"
2446 ." AND package = '$package'"
2447 ." AND version = '$version'";
2448 }
2449 }
2450 }
2452 @packages_list_statements = @new_statement_list;
2453 }
2456 sub parse_package_info {
2457 my ($baseurl, $dist, $section, $session_id)= @_;
2458 my ($package);
2459 if (not defined $session_id) { $session_id = 0; }
2460 my ($path) = ($baseurl =~ m%://[^/]*(.*)$%);
2461 $repo_dirs{ "${repo_path}/pool" } = 1;
2463 foreach $package ("Packages.gz"){
2464 daemon_log("$session_id DEBUG: create_packages_list: fetch $baseurl, $dist, $section", 7);
2465 get_package( "$baseurl/dists/$dist/$section/binary-$arch/$package", "$outdir/$dist/$section", $session_id );
2466 parse_package( "$outdir/$dist/$section", $dist, $path, $session_id );
2467 }
2469 }
2472 sub get_package {
2473 my ($url, $dest, $session_id)= @_;
2474 if (not defined $session_id) { $session_id = 0; }
2476 my $tpath = dirname($dest);
2477 -d "$tpath" || mkpath "$tpath";
2479 # This is ugly, but I've no time to take a look at "how it works in perl"
2480 if(0 == system("wget '$url' -O '$dest' 2>/dev/null") ) {
2481 system("gunzip -cd '$dest' > '$dest.in'");
2482 daemon_log("$session_id DEBUG: run command: gunzip -cd '$dest' > '$dest.in'", 5);
2483 unlink($dest);
2484 daemon_log("$session_id DEBUG: delete file '$dest'", 5);
2485 } else {
2486 daemon_log("$session_id ERROR: create_packages_list_db: get_packages: fetching '$url' failed!", 1);
2487 }
2488 return 0;
2489 }
2492 sub parse_package {
2493 my ($path, $dist, $srv_path, $session_id)= @_;
2494 if (not defined $session_id) { $session_id = 0;}
2495 my ($package, $version, $section, $description);
2496 my $PACKAGES;
2497 my $timestamp = &get_time();
2499 if(not stat("$path.in")) {
2500 daemon_log("$session_id ERROR: create_packages_list: parse_package: file '$path.in' is not readable",1);
2501 return;
2502 }
2504 open($PACKAGES, "<$path.in");
2505 if(not defined($PACKAGES)) {
2506 daemon_log("$session_id ERROR: create_packages_list_db: parse_package: cannot open '$path.in'",1);
2507 return;
2508 }
2510 # Read lines
2511 while (<$PACKAGES>){
2512 my $line = $_;
2513 # Unify
2514 chop($line);
2516 # Use empty lines as a trigger
2517 if ($line =~ /^\s*$/){
2518 my $sql = "INSERT INTO packages_list VALUES ('$dist', '$package', '$version', '$section', '$description', '', '$timestamp')";
2519 push(@packages_list_statements, $sql);
2520 $package = "none";
2521 $version = "none";
2522 $section = "none";
2523 $description = "none";
2524 next;
2525 }
2527 # Trigger for package name
2528 if ($line =~ /^Package:\s/){
2529 ($package)= ($line =~ /^Package: (.*)$/);
2530 next;
2531 }
2533 # Trigger for version
2534 if ($line =~ /^Version:\s/){
2535 ($version)= ($line =~ /^Version: (.*)$/);
2536 next;
2537 }
2539 # Trigger for description
2540 if ($line =~ /^Description:\s/){
2541 ($description)= &encode_base64(($line =~ /^Description: (.*)$/));
2542 next;
2543 }
2545 # Trigger for section
2546 if ($line =~ /^Section:\s/){
2547 ($section)= ($line =~ /^Section: (.*)$/);
2548 next;
2549 }
2551 # Trigger for filename
2552 if ($line =~ /^Filename:\s/){
2553 my ($filename) = ($line =~ /^Filename: (.*)$/);
2554 store_fileinfo( $package, $filename, $dist, $srv_path, $version, $repo_path );
2555 next;
2556 }
2557 }
2559 close( $PACKAGES );
2560 unlink( "$path.in" );
2561 &main::daemon_log("$session_id DEBUG: unlink '$path.in'", 1);
2562 }
2565 sub store_fileinfo {
2566 my( $package, $file, $dist, $path, $vers, $srvdir) = @_;
2568 my %fileinfo = (
2569 'package' => $package,
2570 'dist' => $dist,
2571 'version' => $vers,
2572 );
2574 $repo_files{ "${srvdir}/$file" } = \%fileinfo;
2575 }
2578 sub cleanup_and_extract {
2579 my $fileinfo = $repo_files{ $File::Find::name };
2581 if( defined $fileinfo ) {
2583 my $dir = "$outdir/$fileinfo->{ 'dist' }/debconf.d";
2584 my $sql;
2585 my $package = $fileinfo->{ 'package' };
2586 my $newver = $fileinfo->{ 'version' };
2588 mkpath($dir);
2589 system( "dpkg -e '$File::Find::name' '$dir/DEBIAN'" );
2591 if( -f "$dir/DEBIAN/templates" ) {
2593 daemon_log("DEBUG: Found debconf templates in '$package' - $newver", 5);
2595 my $tmpl= "";
2596 {
2597 local $/=undef;
2598 open FILE, "$dir/DEBIAN/templates";
2599 $tmpl = &encode_base64(<FILE>);
2600 close FILE;
2601 }
2602 rmtree("$dir/DEBIAN/templates");
2604 $sql= "update $main::packages_list_tn set template = '$tmpl' where package = '$package' and version = '$newver';";
2605 push @packages_list_statements, $sql;
2606 }
2607 }
2609 return;
2610 }
2613 sub register_at_foreign_servers {
2614 my ($kernel) = $_[KERNEL];
2616 # hole alle bekannten server aus known_server_db
2617 my $server_sql = "SELECT * FROM $known_server_tn";
2618 my $server_res = $known_server_db->exec_statement($server_sql);
2620 # no entries in known_server_db
2621 if (not ref(@$server_res[0]) eq "ARRAY") {
2622 # TODO
2623 }
2625 # detect already connected clients
2626 my $client_sql = "SELECT * FROM $known_clients_tn";
2627 my $client_res = $known_clients_db->exec_statement($client_sql);
2629 # send my server details to all other gosa-si-server within the network
2630 foreach my $hit (@$server_res) {
2631 my $hostname = @$hit[0];
2632 my $hostkey = &create_passwd;
2634 # add already connected clients to registration message
2635 my $myhash = &create_xml_hash('new_server', $server_address, $hostname);
2636 &add_content2xml_hash($myhash, 'key', $hostkey);
2637 map(&add_content2xml_hash($myhash, 'client', @{$_}[0].",".@{$_}[4]), @$client_res);
2639 # build registration message and send it
2640 my $foreign_server_msg = &create_xml_string($myhash);
2641 my $error = &send_msg_to_target($foreign_server_msg, $hostname, $ServerPackages_key, "new_server", 0);
2642 }
2644 $kernel->delay_set("register_at_foreign_servers", $foreign_servers_register_delay);
2645 return;
2646 }
2649 #==== MAIN = main ==============================================================
2650 # parse commandline options
2651 Getopt::Long::Configure( "bundling" );
2652 GetOptions("h|help" => \&usage,
2653 "c|config=s" => \$cfg_file,
2654 "f|foreground" => \$foreground,
2655 "v|verbose+" => \$verbose,
2656 "no-bus+" => \$no_bus,
2657 "no-arp+" => \$no_arp,
2658 );
2660 # read and set config parameters
2661 &check_cmdline_param ;
2662 &read_configfile;
2663 &check_pid;
2665 $SIG{CHLD} = 'IGNORE';
2667 # forward error messages to logfile
2668 if( ! $foreground ) {
2669 open( STDIN, '+>/dev/null' );
2670 open( STDOUT, '+>&STDIN' );
2671 open( STDERR, '+>&STDIN' );
2672 }
2674 # Just fork, if we are not in foreground mode
2675 if( ! $foreground ) {
2676 chdir '/' or die "Can't chdir to /: $!";
2677 $pid = fork;
2678 setsid or die "Can't start a new session: $!";
2679 umask 0;
2680 } else {
2681 $pid = $$;
2682 }
2684 # Do something useful - put our PID into the pid_file
2685 if( 0 != $pid ) {
2686 open( LOCK_FILE, ">$pid_file" );
2687 print LOCK_FILE "$pid\n";
2688 close( LOCK_FILE );
2689 if( !$foreground ) {
2690 exit( 0 )
2691 };
2692 }
2694 # parse head url and revision from svn
2695 my $server_status_hash = { 'developmental'=>'revision', 'stable'=>'release'};
2696 $server_version =~ /^\$HeadURL: (\S+) \$:\$Rev: (\d+) \$$/;
2697 $server_headURL = defined $1 ? $1 : 'unknown' ;
2698 $server_revision = defined $2 ? $2 : 'unknown' ;
2699 if ($server_headURL =~ /\/tag\// ||
2700 $server_headURL =~ /\/branches\// ) {
2701 $server_status = "stable";
2702 } else {
2703 $server_status = "developmental" ;
2704 }
2707 daemon_log(" ", 1);
2708 daemon_log("$0 started!", 1);
2709 daemon_log("status: $server_status", 1);
2710 daemon_log($server_status_hash->{$server_status}.": $server_revision", 1);
2712 if ($no_bus > 0) {
2713 $bus_activ = "false"
2714 }
2716 # connect to incoming_db
2717 unlink($incoming_file_name);
2718 $incoming_db = GOSA::DBsqlite->new($incoming_file_name);
2719 $incoming_db->create_table($incoming_tn, \@incoming_col_names);
2721 # connect to gosa-si job queue
2722 $job_db = GOSA::DBsqlite->new($job_queue_file_name);
2723 $job_db->create_table($job_queue_tn, \@job_queue_col_names);
2725 # connect to known_clients_db
2726 $known_clients_db = GOSA::DBsqlite->new($known_clients_file_name);
2727 $known_clients_db->create_table($known_clients_tn, \@known_clients_col_names);
2729 # connect to foreign_clients_db
2730 $foreign_clients_db = GOSA::DBsqlite->new($foreign_clients_file_name);
2731 $foreign_clients_db->create_table($foreign_clients_tn, \@foreign_clients_col_names);
2733 # connect to known_server_db
2734 unlink($known_server_file_name);
2735 $known_server_db = GOSA::DBsqlite->new($known_server_file_name);
2736 $known_server_db->create_table($known_server_tn, \@known_server_col_names);
2738 # connect to login_usr_db
2739 $login_users_db = GOSA::DBsqlite->new($login_users_file_name);
2740 $login_users_db->create_table($login_users_tn, \@login_users_col_names);
2742 # connect to fai_server_db and fai_release_db
2743 unlink($fai_server_file_name);
2744 $fai_server_db = GOSA::DBsqlite->new($fai_server_file_name);
2745 $fai_server_db->create_table($fai_server_tn, \@fai_server_col_names);
2747 unlink($fai_release_file_name);
2748 $fai_release_db = GOSA::DBsqlite->new($fai_release_file_name);
2749 $fai_release_db->create_table($fai_release_tn, \@fai_release_col_names);
2751 # connect to packages_list_db
2752 #unlink($packages_list_file_name);
2753 unlink($packages_list_under_construction);
2754 $packages_list_db = GOSA::DBsqlite->new($packages_list_file_name);
2755 $packages_list_db->create_table($packages_list_tn, \@packages_list_col_names);
2757 # connect to messaging_db
2758 $messaging_db = GOSA::DBsqlite->new($messaging_file_name);
2759 $messaging_db->create_table($messaging_tn, \@messaging_col_names);
2762 # create xml object used for en/decrypting
2763 $xml = new XML::Simple();
2766 # foreign servers
2767 my @foreign_server_list;
2769 # add foreign server from cfg file
2770 if ($foreign_server_string ne "") {
2771 my @cfg_foreign_server_list = split(",", $foreign_server_string);
2772 foreach my $foreign_server (@cfg_foreign_server_list) {
2773 push(@foreign_server_list, $foreign_server);
2774 }
2775 }
2777 # add foreign server from dns
2778 my @tmp_servers;
2779 if ( !$server_domain) {
2780 # Try our DNS Searchlist
2781 for my $domain(get_dns_domains()) {
2782 chomp($domain);
2783 my @tmp_domains= &get_server_addresses($domain);
2784 if(@tmp_domains) {
2785 for my $tmp_server(@tmp_domains) {
2786 push @tmp_servers, $tmp_server;
2787 }
2788 }
2789 }
2790 if(@tmp_servers && length(@tmp_servers)==0) {
2791 daemon_log("0 WARNING: no foreign gosa-si-server found in DNS for domain '$server_domain'", 3);
2792 }
2793 } else {
2794 @tmp_servers = &get_server_addresses($server_domain);
2795 if( 0 == @tmp_servers ) {
2796 daemon_log("0 WARNING: no foreign gosa-si-server found in DNS for domain '$server_domain'", 3);
2797 }
2798 }
2799 foreach my $server (@tmp_servers) {
2800 unshift(@foreign_server_list, $server);
2801 }
2802 # eliminate duplicate entries
2803 @foreign_server_list = &del_doubles(@foreign_server_list);
2804 my $all_foreign_server = join(", ", @foreign_server_list);
2805 daemon_log("0 INFO: found foreign server in config file and DNS: $all_foreign_server", 5);
2807 # add all found foreign servers to known_server
2808 my $act_timestamp = &get_time();
2809 foreach my $foreign_server (@foreign_server_list) {
2810 my $res = $known_server_db->add_dbentry( {table=>$known_server_tn,
2811 primkey=>['hostname'],
2812 hostname=>$foreign_server,
2813 status=>'not_jet_registered',
2814 hostkey=>"none",
2815 timestamp=>$act_timestamp,
2816 } );
2817 }
2820 POE::Component::Server::TCP->new(
2821 Port => $server_port,
2822 ClientInput => sub {
2823 my ($kernel, $input) = @_[KERNEL, ARG0];
2824 push(@tasks, $input);
2825 push(@msgs_to_decrypt, $input);
2826 $kernel->yield("msg_to_decrypt");
2827 $kernel->yield("next_task");
2828 },
2829 InlineStates => {
2830 next_task => \&next_task,
2831 msg_to_decrypt => \&msg_to_decrypt,
2832 task_result => \&handle_task_result,
2833 task_done => \&handle_task_done,
2834 task_debug => \&handle_task_debug,
2835 child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!" },
2836 }
2837 );
2839 daemon_log("start socket for incoming xml messages at port '$server_port' ", 1);
2841 # create session for repeatedly checking the job queue for jobs
2842 POE::Session->create(
2843 inline_states => {
2844 _start => \&_start,
2845 register_at_foreign_servers => \®ister_at_foreign_servers,
2846 sig_handler => \&sig_handler,
2847 watch_for_new_messages => \&watch_for_new_messages,
2848 watch_for_delivery_messages => \&watch_for_delivery_messages,
2849 watch_for_done_messages => \&watch_for_done_messages,
2850 watch_for_new_jobs => \&watch_for_new_jobs,
2851 watch_for_done_jobs => \&watch_for_done_jobs,
2852 watch_for_old_known_clients => \&watch_for_old_known_clients,
2853 create_packages_list_db => \&run_create_packages_list_db,
2854 create_fai_server_db => \&run_create_fai_server_db,
2855 create_fai_release_db => \&run_create_fai_release_db,
2856 session_run_result => \&session_run_result,
2857 session_run_debug => \&session_run_debug,
2858 session_run_done => \&session_run_done,
2859 child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!" },
2860 }
2861 );
2864 # import all modules
2865 &import_modules;
2867 # TODO
2868 # check wether all modules are gosa-si valid passwd check
2872 POE::Kernel->run();
2873 exit;