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