1bf9728c3d09c4af3c3c5260ea5b54e5b9a47952
1 #!/usr/bin/perl
2 #===============================================================================
3 #
4 # FILE: gosa-sd
5 #
6 # USAGE: ./gosa-sd
7 #
8 # DESCRIPTION:
9 #
10 # OPTIONS: ---
11 # REQUIREMENTS: libconfig-inifiles-perl libcrypt-rijndael-perl libxml-simple-perl
12 # libdata-dumper-simple-perl libdbd-sqlite3-perl libnet-ldap-perl
13 # libpoe-perl
14 # BUGS: ---
15 # NOTES:
16 # AUTHOR: (Andreas Rettenberger), <rettenberger@gonicus.de>
17 # COMPANY:
18 # VERSION: 1.0
19 # CREATED: 12.09.2007 08:54:41 CEST
20 # REVISION: ---
21 #===============================================================================
24 # TODO
25 #
26 # max_children wird momentan nicht mehr verwendet, jede eingehende nachricht bekommt ein eigenes POE child
28 use strict;
29 use warnings;
30 use Getopt::Long;
31 use Config::IniFiles;
32 use POSIX;
34 use Fcntl;
35 use IO::Socket::INET;
36 use IO::Handle;
37 use IO::Select;
38 use Symbol qw(qualify_to_ref);
39 use Crypt::Rijndael;
40 use MIME::Base64;
41 use Digest::MD5 qw(md5 md5_hex md5_base64);
42 use XML::Simple;
43 use Data::Dumper;
44 use Sys::Syslog qw( :DEFAULT setlogsock);
45 use Cwd;
46 use File::Spec;
47 use File::Basename;
48 use File::Find;
49 use File::Copy;
50 use File::Path;
51 use GOSA::DBsqlite;
52 use GOSA::GosaSupportDaemon;
53 use POE qw(Component::Server::TCP Wheel::Run Filter::Reference);
54 use Net::LDAP;
55 use Net::LDAP::Util qw(:escape);
56 use Time::HiRes qw( usleep);
58 my $modules_path = "/usr/lib/gosa-si/modules";
59 use lib "/usr/lib/gosa-si/modules";
61 # revision number of server and program name
62 my $server_version = '$HeadURL: https://oss.gonicus.de/repositories/gosa/trunk/gosa-si/gosa-si-server $:$Rev: 10826 $';
63 my $server_headURL;
64 my $server_revision;
65 my $server_status;
66 our $prg= basename($0);
68 our $global_kernel;
69 my ($foreground, $ping_timeout);
70 my ($server);
71 my ($gosa_server, $job_queue_timeout, $job_queue_loop_delay);
72 my ($messaging_db_loop_delay);
73 my ($procid, $pid);
74 my ($arp_fifo);
75 my ($xml);
76 my $sources_list;
77 my $max_clients;
78 my %repo_files=();
79 my $repo_path;
80 my %repo_dirs=();
82 # Variables declared in config file are always set to 'our'
83 our (%cfg_defaults, $log_file, $pid_file,
84 $server_ip, $server_port, $ClientPackages_key, $dns_lookup,
85 $arp_activ, $gosa_unit_tag,
86 $GosaPackages_key, $gosa_timeout,
87 $foreign_server_string, $server_domain, $ServerPackages_key, $foreign_servers_register_delay,
88 $wake_on_lan_passwd, $job_synchronization, $modified_jobs_loop_delay,
89 $arp_enabled, $arp_interface,
90 $opsi_enabled, $opsi_server, $opsi_admin, $opsi_password,
91 $new_systems_ou,
92 );
94 # additional variable which should be globaly accessable
95 our $server_address;
96 our $server_mac_address;
97 our $gosa_address;
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);
103 our $known_modules;
104 our $root_uid;
105 our $adm_gid;
108 # specifies the verbosity of the daemon_log
109 $verbose = 0 ;
111 # if foreground is not null, script will be not forked to background
112 $foreground = 0 ;
114 # specifies the timeout seconds while checking the online status of a registrating client
115 $ping_timeout = 5;
117 $no_arp = 0;
118 my $packages_list_under_construction = "/tmp/packages_list_creation_in_progress";
119 my @packages_list_statements;
120 my $watch_for_new_jobs_in_progress = 0;
122 # holds all incoming decrypted messages
123 our $incoming_db;
124 our $incoming_tn = 'incoming';
125 my $incoming_file_name;
126 my @incoming_col_names = ("id INTEGER PRIMARY KEY",
127 "timestamp DEFAULT 'none'",
128 "headertag DEFAULT 'none'",
129 "targettag DEFAULT 'none'",
130 "xmlmessage DEFAULT 'none'",
131 "module DEFAULT 'none'",
132 "sessionid DEFAULT '0'",
133 );
135 # holds all gosa jobs
136 our $job_db;
137 our $job_queue_tn = 'jobs';
138 my $job_queue_file_name;
139 my @job_queue_col_names = ("id INTEGER PRIMARY KEY",
140 "timestamp DEFAULT 'none'",
141 "status DEFAULT 'none'",
142 "result DEFAULT 'none'",
143 "progress DEFAULT 'none'",
144 "headertag DEFAULT 'none'",
145 "targettag DEFAULT 'none'",
146 "xmlmessage DEFAULT 'none'",
147 "macaddress DEFAULT 'none'",
148 "plainname DEFAULT 'none'",
149 "siserver DEFAULT 'none'",
150 "modified DEFAULT '0'",
151 );
153 # holds all other gosa-si-server
154 our $known_server_db;
155 our $known_server_tn = "known_server";
156 my $known_server_file_name;
157 my @known_server_col_names = ("hostname", "macaddress", "status", "hostkey", "loaded_modules", "timestamp");
159 # holds all registrated clients
160 our $known_clients_db;
161 our $known_clients_tn = "known_clients";
162 my $known_clients_file_name;
163 my @known_clients_col_names = ("hostname", "status", "hostkey", "timestamp", "macaddress", "events", "keylifetime");
165 # holds all registered clients at a foreign server
166 our $foreign_clients_db;
167 our $foreign_clients_tn = "foreign_clients";
168 my $foreign_clients_file_name;
169 my @foreign_clients_col_names = ("hostname", "macaddress", "regserver", "timestamp");
171 # holds all logged in user at each client
172 our $login_users_db;
173 our $login_users_tn = "login_users";
174 my $login_users_file_name;
175 my @login_users_col_names = ("client", "user", "timestamp", "regserver");
177 # holds all fai server, the debian release and tag
178 our $fai_server_db;
179 our $fai_server_tn = "fai_server";
180 my $fai_server_file_name;
181 our @fai_server_col_names = ("timestamp", "server", "release", "sections", "tag");
183 our $fai_release_db;
184 our $fai_release_tn = "fai_release";
185 my $fai_release_file_name;
186 our @fai_release_col_names = ("timestamp", "release", "class", "type", "state");
188 # holds all packages available from different repositories
189 our $packages_list_db;
190 our $packages_list_tn = "packages_list";
191 my $packages_list_file_name;
192 our @packages_list_col_names = ("distribution", "package", "version", "section", "description", "template", "timestamp");
193 my $outdir = "/tmp/packages_list_db";
194 my $arch = "i386";
196 # holds all messages which should be delivered to a user
197 our $messaging_db;
198 our $messaging_tn = "messaging";
199 our @messaging_col_names = ("id INTEGER", "subject", "message_from", "message_to",
200 "flag", "direction", "delivery_time", "message", "timestamp" );
201 my $messaging_file_name;
203 # path to directory to store client install log files
204 our $client_fai_log_dir = "/var/log/fai";
206 # queue which stores taskes until one of the $max_children children are ready to process the task
207 my @tasks = qw();
208 my @msgs_to_decrypt = qw();
209 my $max_children = 2;
212 # loop delay for job queue to look for opsi jobs
213 my $job_queue_opsi_delay = 10;
214 our $opsi_client;
215 our $opsi_url;
217 # Lifetime of logged in user information. If no update information comes after n seconds,
218 # the user is expeceted to be no longer logged in or the host is no longer running. Because
219 # of this, the user is deleted from login_users_db
220 our $logged_in_user_date_of_expiry = 600;
223 %cfg_defaults = (
224 "general" => {
225 "log-file" => [\$log_file, "/var/run/".$prg.".log"],
226 "pid-file" => [\$pid_file, "/var/run/".$prg.".pid"],
227 },
228 "server" => {
229 "ip" => [\$server_ip, "0.0.0.0"],
230 "port" => [\$server_port, "20081"],
231 "known-clients" => [\$known_clients_file_name, '/var/lib/gosa-si/clients.db' ],
232 "known-servers" => [\$known_server_file_name, '/var/lib/gosa-si/servers.db'],
233 "incoming" => [\$incoming_file_name, '/var/lib/gosa-si/incoming.db'],
234 "login-users" => [\$login_users_file_name, '/var/lib/gosa-si/users.db'],
235 "fai-server" => [\$fai_server_file_name, '/var/lib/gosa-si/fai_server.db'],
236 "fai-release" => [\$fai_release_file_name, '/var/lib/gosa-si/fai_release.db'],
237 "packages-list" => [\$packages_list_file_name, '/var/lib/gosa-si/packages.db'],
238 "messaging" => [\$messaging_file_name, '/var/lib/gosa-si/messaging.db'],
239 "foreign-clients" => [\$foreign_clients_file_name, '/var/lib/gosa-si/foreign_clients.db'],
240 "source-list" => [\$sources_list, '/etc/apt/sources.list'],
241 "repo-path" => [\$repo_path, '/srv/www/repository'],
242 "ldap-uri" => [\$ldap_uri, ""],
243 "ldap-base" => [\$ldap_base, ""],
244 "ldap-admin-dn" => [\$ldap_admin_dn, ""],
245 "ldap-admin-password" => [\$ldap_admin_password, ""],
246 "gosa-unit-tag" => [\$gosa_unit_tag, ""],
247 "max-clients" => [\$max_clients, 10],
248 "wol-password" => [\$wake_on_lan_passwd, ""],
249 },
250 "GOsaPackages" => {
251 "job-queue" => [\$job_queue_file_name, '/var/lib/gosa-si/jobs.db'],
252 "job-queue-loop-delay" => [\$job_queue_loop_delay, 3],
253 "messaging-db-loop-delay" => [\$messaging_db_loop_delay, 3],
254 "key" => [\$GosaPackages_key, "none"],
255 "new-systems-ou" => [\$new_systems_ou, 'ou=workstations,ou=systems'],
256 },
257 "ClientPackages" => {
258 "key" => [\$ClientPackages_key, "none"],
259 "user-date-of-expiry" => [\$logged_in_user_date_of_expiry, 600],
260 },
261 "ServerPackages"=> {
262 "address" => [\$foreign_server_string, ""],
263 "dns-lookup" => [\$dns_lookup, "true"],
264 "domain" => [\$server_domain, ""],
265 "key" => [\$ServerPackages_key, "none"],
266 "key-lifetime" => [\$foreign_servers_register_delay, 120],
267 "job-synchronization-enabled" => [\$job_synchronization, "true"],
268 "synchronization-loop" => [\$modified_jobs_loop_delay, 5],
269 },
270 "ArpHandler" => {
271 "enabled" => [\$arp_enabled, "true"],
272 "interface" => [\$arp_interface, "all"],
273 },
274 "Opsi" => {
275 "enabled" => [\$opsi_enabled, "false"],
276 "server" => [\$opsi_server, "localhost"],
277 "admin" => [\$opsi_admin, "opsi-admin"],
278 "password" => [\$opsi_password, "secret"],
279 },
281 );
284 #=== FUNCTION ================================================================
285 # NAME: usage
286 # PARAMETERS: nothing
287 # RETURNS: nothing
288 # DESCRIPTION: print out usage text to STDERR
289 #===============================================================================
290 sub usage {
291 print STDERR << "EOF" ;
292 usage: $prg [-hvf] [-c config]
294 -h : this (help) message
295 -c <file> : config file
296 -f : foreground, process will not be forked to background
297 -v : be verbose (multiple to increase verbosity)
298 -no-arp : starts $prg without connection to arp module
300 EOF
301 print "\n" ;
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 if(not defined open( LOG_HANDLE, ">>$log_file" )) {
321 print STDERR "cannot open $log_file: $!";
322 return
323 }
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 # ArpHandler switch
446 if( $file =~ /ArpHandler.pm/ ) {
447 if( $arp_enabled eq "false" ) { next; }
448 }
450 eval { require $file; };
451 if ($@) {
452 daemon_log("0 ERROR: gosa-si-server could not load module $file", 1);
453 daemon_log("$@", 1);
454 exit;
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, $event_hash) = @{$info};
460 $known_modules->{$mod_name} = $info;
461 daemon_log("0 INFO: module $mod_name loaded", 5);
462 }
463 }
464 }
466 close (DIR);
467 }
469 #=== FUNCTION ================================================================
470 # NAME: password_check
471 # PARAMETERS: nothing
472 # RETURNS: nothing
473 # DESCRIPTION: escalates an critical error if two modules exist which are avaialable by
474 # the same password
475 #===============================================================================
476 sub password_check {
477 my $passwd_hash = {};
478 while (my ($mod_name, $mod_info) = each %$known_modules) {
479 my $mod_passwd = @$mod_info[1];
480 if (not defined $mod_passwd) { next; }
481 if (not exists $passwd_hash->{$mod_passwd}) {
482 $passwd_hash->{$mod_passwd} = $mod_name;
484 # escalates critical error
485 } else {
486 &daemon_log("0 ERROR: two loaded modules do have the same password. Please modify the 'key'-parameter in config file");
487 &daemon_log("0 ERROR: module='$mod_name' and module='".$passwd_hash->{$mod_passwd}."'");
488 exit( -1 );
489 }
490 }
492 }
495 #=== FUNCTION ================================================================
496 # NAME: sig_int_handler
497 # PARAMETERS: signal - string - signal arose from system
498 # RETURNS: nothing
499 # DESCRIPTION: handels tasks to be done befor signal becomes active
500 #===============================================================================
501 sub sig_int_handler {
502 my ($signal) = @_;
504 # if (defined($ldap_handle)) {
505 # $ldap_handle->disconnect;
506 # }
507 # TODO alle verbliebenden ldap verbindungen aus allen heaps beenden
510 daemon_log("shutting down gosa-si-server", 1);
511 system("kill `ps -C gosa-si-server -o pid=`");
512 }
513 $SIG{INT} = \&sig_int_handler;
516 sub check_key_and_xml_validity {
517 my ($crypted_msg, $module_key, $session_id) = @_;
518 my $msg;
519 my $msg_hash;
520 my $error_string;
521 eval{
522 $msg = &decrypt_msg($crypted_msg, $module_key);
524 if ($msg =~ /<xml>/i){
525 $msg =~ s/\s+/ /g; # just for better daemon_log
526 daemon_log("$session_id DEBUG: decrypted_msg: \n$msg", 8);
527 $msg_hash = $xml->XMLin($msg, ForceArray=>1);
529 ##############
530 # check header
531 if( not exists $msg_hash->{'header'} ) { die "no header specified"; }
532 my $header_l = $msg_hash->{'header'};
533 if( 1 > @{$header_l} ) { die 'empty header tag'; }
534 if( 1 < @{$header_l} ) { die 'more than one header specified'; }
535 my $header = @{$header_l}[0];
536 if( 0 == length $header) { die 'empty string in header tag'; }
538 ##############
539 # check source
540 if( not exists $msg_hash->{'source'} ) { die "no source specified"; }
541 my $source_l = $msg_hash->{'source'};
542 if( 1 > @{$source_l} ) { die 'empty source tag'; }
543 if( 1 < @{$source_l} ) { die 'more than one source specified'; }
544 my $source = @{$source_l}[0];
545 if( 0 == length $source) { die 'source error'; }
547 ##############
548 # check target
549 if( not exists $msg_hash->{'target'} ) { die "no target specified"; }
550 my $target_l = $msg_hash->{'target'};
551 if( 1 > @{$target_l} ) { die 'empty target tag'; }
552 }
553 };
554 if($@) {
555 daemon_log("$session_id ERROR: do not understand the message: $@", 1);
556 $msg = undef;
557 $msg_hash = undef;
558 }
560 return ($msg, $msg_hash);
561 }
564 sub check_outgoing_xml_validity {
565 my ($msg, $session_id) = @_;
567 my $msg_hash;
568 eval{
569 $msg_hash = $xml->XMLin($msg, ForceArray=>1);
571 ##############
572 # check header
573 my $header_l = $msg_hash->{'header'};
574 if( 1 != @{$header_l} ) {
575 die 'no or more than one headers specified';
576 }
577 my $header = @{$header_l}[0];
578 if( 0 == length $header) {
579 die 'header has length 0';
580 }
582 ##############
583 # check source
584 my $source_l = $msg_hash->{'source'};
585 if( 1 != @{$source_l} ) {
586 die 'no or more than 1 sources specified';
587 }
588 my $source = @{$source_l}[0];
589 if( 0 == length $source) {
590 die 'source has length 0';
591 }
592 unless( $source =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
593 $source =~ /^GOSA$/i ) {
594 die "source '$source' is neither a complete ip-address with port nor 'GOSA'";
595 }
597 ##############
598 # check target
599 my $target_l = $msg_hash->{'target'};
600 if( 0 == @{$target_l} ) {
601 die "no targets specified";
602 }
603 foreach my $target (@$target_l) {
604 if( 0 == length $target) {
605 die "target has length 0";
606 }
607 unless( $target =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
608 $target =~ /^GOSA$/i ||
609 $target =~ /^\*$/ ||
610 $target =~ /KNOWN_SERVER/i ||
611 $target =~ /JOBDB/i ||
612 $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 ){
613 die "target '$target' is not a complete ip-address with port or a valid target name or a mac-address";
614 }
615 }
616 };
617 if($@) {
618 daemon_log("$session_id ERROR: outgoing msg is not gosa-si envelope conform: $@", 1);
619 daemon_log("$@ ".(defined($msg) && length($msg)>0)?$msg:"Empty Message", 1);
620 $msg_hash = undef;
621 }
623 return ($msg_hash);
624 }
627 sub input_from_known_server {
628 my ($input, $remote_ip, $session_id) = @_ ;
629 my ($msg, $msg_hash, $module);
631 my $sql_statement= "SELECT * FROM known_server";
632 my $query_res = $known_server_db->select_dbentry( $sql_statement );
634 while( my ($hit_num, $hit) = each %{ $query_res } ) {
635 my $host_name = $hit->{hostname};
636 if( not $host_name =~ "^$remote_ip") {
637 next;
638 }
639 my $host_key = $hit->{hostkey};
640 daemon_log("$session_id DEBUG: input_from_known_server: host_name: $host_name", 7);
641 daemon_log("$session_id DEBUG: input_from_known_server: host_key: $host_key", 7);
643 # check if module can open msg envelope with module key
644 my ($tmp_msg, $tmp_msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
645 if( (!$tmp_msg) || (!$tmp_msg_hash) ) {
646 daemon_log("$session_id DEBUG: input_from_known_server: deciphering raise error", 7);
647 daemon_log("$@", 8);
648 next;
649 }
650 else {
651 $msg = $tmp_msg;
652 $msg_hash = $tmp_msg_hash;
653 $module = "ServerPackages";
654 last;
655 }
656 }
658 if( (!$msg) || (!$msg_hash) || (!$module) ) {
659 daemon_log("$session_id DEBUG: Incoming message is not from a known server", 7);
660 }
662 return ($msg, $msg_hash, $module);
663 }
666 sub input_from_known_client {
667 my ($input, $remote_ip, $session_id) = @_ ;
668 my ($msg, $msg_hash, $module);
670 my $sql_statement= "SELECT * FROM known_clients";
671 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
672 while( my ($hit_num, $hit) = each %{ $query_res } ) {
673 my $host_name = $hit->{hostname};
674 if( not $host_name =~ /^$remote_ip:\d*$/) {
675 next;
676 }
677 my $host_key = $hit->{hostkey};
678 &daemon_log("$session_id DEBUG: input_from_known_client: host_name: $host_name", 7);
679 &daemon_log("$session_id DEBUG: input_from_known_client: host_key: $host_key", 7);
681 # check if module can open msg envelope with module key
682 ($msg, $msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
684 if( (!$msg) || (!$msg_hash) ) {
685 &daemon_log("$session_id DEGUG: input_from_known_client: deciphering raise error", 7);
686 &daemon_log("$@", 8);
687 next;
688 }
689 else {
690 $module = "ClientPackages";
691 last;
692 }
693 }
695 if( (!$msg) || (!$msg_hash) || (!$module) ) {
696 &daemon_log("$session_id DEBUG: Incoming message is not from a known client", 7);
697 }
699 return ($msg, $msg_hash, $module);
700 }
703 sub input_from_unknown_host {
704 no strict "refs";
705 my ($input, $session_id) = @_ ;
706 my ($msg, $msg_hash, $module);
707 my $error_string;
709 my %act_modules = %$known_modules;
711 while( my ($mod, $info) = each(%act_modules)) {
713 # check a key exists for this module
714 my $module_key = ${$mod."_key"};
715 if( not defined $module_key ) {
716 if( $mod eq 'ArpHandler' ) {
717 next;
718 }
719 daemon_log("$session_id ERROR: no key specified in config file for $mod", 1);
720 next;
721 }
722 daemon_log("$session_id DEBUG: $mod: $module_key", 7);
724 # check if module can open msg envelope with module key
725 ($msg, $msg_hash) = &check_key_and_xml_validity($input, $module_key, $session_id);
726 if( (not defined $msg) || (not defined $msg_hash) ) {
727 next;
728 } else {
729 $module = $mod;
730 last;
731 }
732 }
734 if( (!$msg) || (!$msg_hash) || (!$module)) {
735 daemon_log("$session_id DEBUG: Incoming message is not from an unknown host", 7);
736 }
738 return ($msg, $msg_hash, $module);
739 }
742 sub create_ciphering {
743 my ($passwd) = @_;
744 if((!defined($passwd)) || length($passwd)==0) {
745 $passwd = "";
746 }
747 $passwd = substr(md5_hex("$passwd") x 32, 0, 32);
748 my $iv = substr(md5_hex('GONICUS GmbH'),0, 16);
749 my $my_cipher = Crypt::Rijndael->new($passwd , Crypt::Rijndael::MODE_CBC());
750 $my_cipher->set_iv($iv);
751 return $my_cipher;
752 }
755 sub encrypt_msg {
756 my ($msg, $key) = @_;
757 my $my_cipher = &create_ciphering($key);
758 my $len;
759 {
760 use bytes;
761 $len= 16-length($msg)%16;
762 }
763 $msg = "\0"x($len).$msg;
764 $msg = $my_cipher->encrypt($msg);
765 chomp($msg = &encode_base64($msg));
766 # there are no newlines allowed inside msg
767 $msg=~ s/\n//g;
768 return $msg;
769 }
772 sub decrypt_msg {
774 my ($msg, $key) = @_ ;
775 $msg = &decode_base64($msg);
776 my $my_cipher = &create_ciphering($key);
777 $msg = $my_cipher->decrypt($msg);
778 $msg =~ s/\0*//g;
779 return $msg;
780 }
783 sub get_encrypt_key {
784 my ($target) = @_ ;
785 my $encrypt_key;
786 my $error = 0;
788 # target can be in known_server
789 if( not defined $encrypt_key ) {
790 my $sql_statement= "SELECT * FROM known_server WHERE hostname='$target'";
791 my $query_res = $known_server_db->select_dbentry( $sql_statement );
792 while( my ($hit_num, $hit) = each %{ $query_res } ) {
793 my $host_name = $hit->{hostname};
794 if( $host_name ne $target ) {
795 next;
796 }
797 $encrypt_key = $hit->{hostkey};
798 last;
799 }
800 }
802 # target can be in known_client
803 if( not defined $encrypt_key ) {
804 my $sql_statement= "SELECT * FROM known_clients WHERE hostname='$target'";
805 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
806 while( my ($hit_num, $hit) = each %{ $query_res } ) {
807 my $host_name = $hit->{hostname};
808 if( $host_name ne $target ) {
809 next;
810 }
811 $encrypt_key = $hit->{hostkey};
812 last;
813 }
814 }
816 return $encrypt_key;
817 }
820 #=== FUNCTION ================================================================
821 # NAME: open_socket
822 # PARAMETERS: PeerAddr string something like 192.168.1.1 or 192.168.1.1:10000
823 # [PeerPort] string necessary if port not appended by PeerAddr
824 # RETURNS: socket IO::Socket::INET
825 # DESCRIPTION: open a socket to PeerAddr
826 #===============================================================================
827 sub open_socket {
828 my ($PeerAddr, $PeerPort) = @_ ;
829 if(defined($PeerPort)){
830 $PeerAddr = $PeerAddr.":".$PeerPort;
831 }
832 my $socket;
833 $socket = new IO::Socket::INET(PeerAddr => $PeerAddr,
834 Porto => "tcp",
835 Type => SOCK_STREAM,
836 Timeout => 5,
837 );
838 if(not defined $socket) {
839 return;
840 }
841 # &daemon_log("DEBUG: open_socket: $PeerAddr", 7);
842 return $socket;
843 }
846 #sub get_local_ip_for_remote_ip {
847 # my $remote_ip= shift;
848 # my $result="0.0.0.0";
849 #
850 # if($remote_ip =~ /^(\d\d?\d?\.){3}\d\d?\d?$/) {
851 # if($remote_ip eq "127.0.0.1") {
852 # $result = "127.0.0.1";
853 # } else {
854 # my $PROC_NET_ROUTE= ('/proc/net/route');
855 #
856 # open(PROC_NET_ROUTE, "<$PROC_NET_ROUTE")
857 # or die "Could not open $PROC_NET_ROUTE";
858 #
859 # my @ifs = <PROC_NET_ROUTE>;
860 #
861 # close(PROC_NET_ROUTE);
862 #
863 # # Eat header line
864 # shift @ifs;
865 # chomp @ifs;
866 # foreach my $line(@ifs) {
867 # my ($Iface,$Destination,$Gateway,$Flags,$RefCnt,$Use,$Metric,$Mask,$MTU,$Window,$IRTT)=split(/\s/, $line);
868 # my $destination;
869 # my $mask;
870 # my ($d,$c,$b,$a)=unpack('a2 a2 a2 a2', $Destination);
871 # $destination= sprintf("%d.%d.%d.%d", hex($a), hex($b), hex($c), hex($d));
872 # ($d,$c,$b,$a)=unpack('a2 a2 a2 a2', $Mask);
873 # $mask= sprintf("%d.%d.%d.%d", hex($a), hex($b), hex($c), hex($d));
874 # if(new NetAddr::IP($remote_ip)->within(new NetAddr::IP($destination, $mask))) {
875 # # destination matches route, save mac and exit
876 # $result= &get_ip($Iface);
877 # last;
878 # }
879 # }
880 # }
881 # } else {
882 # daemon_log("0 WARNING: get_local_ip_for_remote_ip() was called with a non-ip parameter: '$remote_ip'", 1);
883 # }
884 # return $result;
885 #}
888 sub send_msg_to_target {
889 my ($msg, $address, $encrypt_key, $msg_header, $session_id) = @_ ;
890 my $error = 0;
891 my $header;
892 my $timestamp = &get_time();
893 my $new_status;
894 my $act_status;
895 my ($sql_statement, $res);
897 if( $msg_header ) {
898 $header = "'$msg_header'-";
899 } else {
900 $header = "";
901 }
903 # Patch the source ip
904 if($msg =~ /<source>0\.0\.0\.0:\d*?<\/source>/) {
905 my $remote_ip = &get_local_ip_for_remote_ip(sprintf("%s", $address =~ /^([0-9\.]*?):.*$/));
906 $msg =~ s/<source>(0\.0\.0\.0):(\d*?)<\/source>/<source>$remote_ip:$2<\/source>/s;
907 }
909 # encrypt xml msg
910 my $crypted_msg = &encrypt_msg($msg, $encrypt_key);
912 # opensocket
913 my $socket = &open_socket($address);
914 if( !$socket ) {
915 daemon_log("$session_id WARNING: cannot send ".$header."msg to $address , host not reachable", 3);
916 $error++;
917 }
919 if( $error == 0 ) {
920 # send xml msg
921 print $socket $crypted_msg."\n";
923 daemon_log("$session_id INFO: send ".$header."msg to $address", 5);
924 daemon_log("$session_id DEBUG: message:\n$msg", 9);
926 }
928 # close socket in any case
929 if( $socket ) {
930 close $socket;
931 }
933 if( $error > 0 ) { $new_status = "down"; }
934 else { $new_status = $msg_header; }
937 # known_clients
938 $sql_statement = "SELECT * FROM $known_clients_tn WHERE hostname='$address'";
939 $res = $known_clients_db->select_dbentry($sql_statement);
940 if( keys(%$res) == 1) {
941 $act_status = exists $res->{1}->{'status'} ? $res->{1}->{'status'} : "";
942 if ($act_status eq "down" && $new_status eq "down") {
943 $sql_statement = "DELETE FROM known_clients WHERE hostname='$address'";
944 $res = $known_clients_db->del_dbentry($sql_statement);
945 daemon_log("$session_id WARNING: failed 2x to send msg to host '$address', delete host from known_clients", 3);
946 } else {
947 $sql_statement = "UPDATE known_clients SET status='$new_status', timestamp='$timestamp' WHERE hostname='$address'";
948 $res = $known_clients_db->update_dbentry($sql_statement);
949 if($new_status eq "down"){
950 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
951 } else {
952 daemon_log("$session_id INFO: set '$address' from status '$act_status' to '$new_status'", 5);
953 }
954 }
955 }
957 # known_server
958 $sql_statement = "SELECT * FROM $known_server_tn WHERE hostname='$address'";
959 $res = $known_server_db->select_dbentry($sql_statement);
960 if( keys(%$res) == 1) {
961 $act_status = exists $res->{1}->{'status'} ? $res->{1}->{'status'} : "";
962 if ($act_status eq "down" && $new_status eq "down") {
963 $sql_statement = "DELETE FROM known_server WHERE hostname='$address'";
964 $res = $known_server_db->del_dbentry($sql_statement);
965 daemon_log("$session_id WARNING: failed 2x to send a message to host '$address', delete host from known_server", 3);
966 }
967 else {
968 $sql_statement = "UPDATE known_server SET status='$new_status', timestamp='$timestamp' WHERE hostname='$address'";
969 $res = $known_server_db->update_dbentry($sql_statement);
970 if($new_status eq "down"){
971 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
972 } else {
973 daemon_log("$session_id INFO: set '$address' from status '$act_status' to '$new_status'", 5);
974 }
975 }
976 }
977 return $error;
978 }
981 sub update_jobdb_status_for_send_msgs {
982 my ($session_id, $answer, $error) = @_;
983 &daemon_log("$session_id DEBUG: try to update job status", 7);
984 if( $answer =~ /<jobdb_id>(\d+)<\/jobdb_id>/ ) {
985 my $jobdb_id = $1;
986 $answer =~ /<header>(.*)<\/header>/;
987 my $job_header = $1;
988 $answer =~ /<target>(.*)<\/target>/;
989 my $job_target = $1;
991 # sending msg failed
992 if( $error ) {
994 # set jobs to done, jobs do not need to deliver their message in any case
995 if (($job_header eq "trigger_action_localboot")
996 ||($job_header eq "trigger_action_lock")
997 ||($job_header eq "trigger_action_halt")
998 ) {
999 my $sql_statement = "UPDATE $job_queue_tn SET status='done' WHERE id=$jobdb_id";
1000 &daemon_log("$session_id DEBUG: $sql_statement", 7);
1001 my $res = $job_db->update_dbentry($sql_statement);
1003 # reactivate jobs, jobs need to deliver their message
1004 } elsif (($job_header eq "trigger_action_activate")
1005 ||($job_header eq "trigger_action_update")
1006 ||($job_header eq "trigger_action_reinstall")
1007 ||($job_header eq "trigger_activate_new")
1008 ) {
1009 &reactivate_job_with_delay($session_id, $job_target, $job_header, 30 );
1011 # for all other messages
1012 } else {
1013 my $sql_statement = "UPDATE $job_queue_tn ".
1014 "SET status='error', result='can not deliver msg, please consult log file' ".
1015 "WHERE id=$jobdb_id";
1016 my $res = $job_db->update_dbentry($sql_statement);
1017 &daemon_log("$session_id DEBUG: $sql_statement", 7);
1018 }
1020 # sending msg was successful
1021 } else {
1022 # set jobs localboot, lock, activate, halt, reboot and wake to done
1023 # jobs reinstall, update, inst_update do themself setting to done
1024 if (($job_header eq "trigger_action_localboot")
1025 ||($job_header eq "trigger_action_lock")
1026 ||($job_header eq "trigger_action_activate")
1027 ||($job_header eq "trigger_action_halt")
1028 ||($job_header eq "trigger_action_reboot")
1029 ||($job_header eq "trigger_action_wake")
1030 ||($job_header eq "trigger_wake")
1031 ) {
1033 my $sql_statement = "UPDATE $job_queue_tn ".
1034 "SET status='done' ".
1035 "WHERE id=$jobdb_id AND status='processed'";
1036 my $res = $job_db->update_dbentry($sql_statement);
1037 &daemon_log("$session_id DEBUG: $sql_statement", 7);
1038 } else {
1039 &daemon_log("$session_id WARNING: sending message succeed but cannot update job status.", 3);
1040 }
1041 }
1042 } else {
1043 &daemon_log("$session_id ERROR: cannot update job status, msg has no jobdb_id-tag: $answer", 1);
1044 }
1045 }
1047 sub reactivate_job_with_delay {
1048 my ($session_id, $target, $header, $delay) = @_ ;
1049 # Sometimes the client is still booting or does not wake up, in this case reactivate the job (if it exists) with a delay of n sec
1051 if (not defined $delay) { $delay = 30 } ;
1052 my $delay_timestamp = &calc_timestamp(&get_time(), "plus", $delay);
1054 my $sql = "UPDATE $job_queue_tn Set timestamp='$delay_timestamp', status='waiting' WHERE (macaddress='$target' AND headertag='$header')";
1055 my $res = $job_db->update_dbentry($sql);
1056 daemon_log("$session_id INFO: '$header'-job will be reactivated at '$delay_timestamp' ".
1057 "cause client '$target' is currently not available", 5);
1058 daemon_log("$session_id $sql", 7);
1059 return;
1060 }
1063 sub sig_handler {
1064 my ($kernel, $signal) = @_[KERNEL, ARG0] ;
1065 daemon_log("0 INFO got signal '$signal'", 1);
1066 $kernel->sig_handled();
1067 return;
1068 }
1071 sub msg_to_decrypt {
1072 my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
1073 my $session_id = $session->ID;
1074 my ($msg, $msg_hash, $module);
1075 my $error = 0;
1077 # fetch new msg out of @msgs_to_decrypt
1078 my $tmp_next_msg = shift @msgs_to_decrypt;
1079 my ($next_msg, $msg_source) = split(/;/, $tmp_next_msg);
1081 # msg is from a new client or gosa
1082 ($msg, $msg_hash, $module) = &input_from_unknown_host($next_msg, $session_id);
1083 # msg is from a gosa-si-server
1084 if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1085 ($msg, $msg_hash, $module) = &input_from_known_server($next_msg, $heap->{'remote_ip'}, $session_id);
1086 }
1087 # msg is from a gosa-si-client
1088 if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1089 ($msg, $msg_hash, $module) = &input_from_known_client($next_msg, $heap->{'remote_ip'}, $session_id);
1090 }
1091 # an error occurred
1092 if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1093 # if an incoming msg could not be decrypted (maybe a wrong key), send client a ping. If the client
1094 # could not understand a msg from its server the client cause a re-registering process
1095 my $ping_msg = "<xml> <header>gosa_ping</header> <source>$server_address</source><target>$msg_source</target></xml>";
1096 my ($test_error, $test_error_string) = &send_msg_to_target($ping_msg, "$msg_source", "dummy-key", "gosa_ping", $session_id);
1098 daemon_log("$session_id WARNING cannot understand incoming msg, send 'ping'-msg to all host with ip '".$heap->{remote_ip}.
1099 "' to cause a re-registering of the client if necessary", 3);
1100 $error++;
1101 }
1103 my $header;
1104 my $target;
1105 my $source;
1106 my $done = 0;
1107 my $sql;
1108 my $res;
1110 # check whether this message should be processed here
1111 if ($error == 0) {
1112 $header = @{$msg_hash->{'header'}}[0];
1113 $target = @{$msg_hash->{'target'}}[0];
1114 $source = @{$msg_hash->{'source'}}[0];
1115 my $not_found_in_known_clients_db = 0;
1116 my $not_found_in_known_server_db = 0;
1117 my $not_found_in_foreign_clients_db = 0;
1118 my $local_address;
1119 my $local_mac;
1120 my ($target_ip, $target_port) = split(':', $target);
1122 # Determine the local ip address if target is an ip address
1123 if ($target =~ /^\d+\.\d+\.\d+\.\d+:\d+$/) {
1124 $local_address = &get_local_ip_for_remote_ip($target_ip).":$server_port";
1125 } else {
1126 $local_address = $server_address;
1127 }
1129 # Determine the local mac address if target is a mac address
1130 if ($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) {
1131 my $loc_ip = &get_local_ip_for_remote_ip($heap->{'remote_ip'});
1132 my $network_interface= &get_interface_for_ip($loc_ip);
1133 $local_mac = &get_mac_for_interface($network_interface);
1134 } else {
1135 $local_mac = $server_mac_address;
1136 }
1138 # target and source is equal to GOSA -> process here
1139 if (not $done) {
1140 if ($target eq "GOSA" && $source eq "GOSA") {
1141 $done = 1;
1142 &daemon_log("$session_id DEBUG: target and source is 'GOSA' -> process here", 7);
1143 }
1144 }
1146 # target is own address without forward_to_gosa-tag -> process here
1147 if (not $done) {
1148 #if ((($target eq $local_address) || ($target eq $local_mac) ) && (not exists $msg_hash->{'forward_to_gosa'})) {
1149 if (($target eq $local_address) && (not exists $msg_hash->{'forward_to_gosa'})) {
1150 $done = 1;
1151 if ($source eq "GOSA") {
1152 $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1153 }
1154 &daemon_log("$session_id DEBUG: target is own address without forward_to_gosa-tag -> process here", 7);
1155 }
1156 }
1158 # target is a client address in known_clients -> process here
1159 if (not $done) {
1160 $sql = "SELECT * FROM $known_clients_tn WHERE (hostname='$target' OR macaddress LIKE '$target')";
1161 $res = $known_clients_db->select_dbentry($sql);
1162 if (keys(%$res) > 0) {
1163 $done = 1;
1164 my $hostname = $res->{1}->{'hostname'};
1165 $msg =~ s/<target>$target<\/target>/<target>$hostname<\/target>/;
1166 my $local_address = &get_local_ip_for_remote_ip($target_ip).":$server_port";
1167 if ($source eq "GOSA") {
1168 $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1169 }
1170 &daemon_log("$session_id DEBUG: target is a client address in known_clients -> process here", 7);
1172 } else {
1173 $not_found_in_known_clients_db = 1;
1174 }
1175 }
1177 # target ist own address with forward_to_gosa-tag not pointing to myself -> process here
1178 if (not $done) {
1179 my $forward_to_gosa = @{$msg_hash->{'forward_to_gosa'}}[0];
1180 my $gosa_at;
1181 my $gosa_session_id;
1182 if (($target eq $local_address) && (defined $forward_to_gosa)){
1183 my ($gosa_at, $gosa_session_id) = split(/,/, $forward_to_gosa);
1184 if ($gosa_at ne $local_address) {
1185 $done = 1;
1186 &daemon_log("$session_id DEBUG: target is own address with forward_to_gosa-tag not pointing to myself -> process here", 7);
1187 }
1188 }
1189 }
1191 # if message should be processed here -> add message to incoming_db
1192 if ($done) {
1193 # if a job or a gosa message comes from a foreign server, fake module to GosaPackages
1194 # so gosa-si-server knows how to process this kind of messages
1195 if ($header =~ /^gosa_/ || $header =~ /^job_/) {
1196 $module = "GosaPackages";
1197 }
1199 my $res = $incoming_db->add_dbentry( {table=>$incoming_tn,
1200 primkey=>[],
1201 headertag=>$header,
1202 targettag=>$target,
1203 xmlmessage=>&encode_base64($msg),
1204 timestamp=>&get_time,
1205 module=>$module,
1206 sessionid=>$session_id,
1207 } );
1208 }
1210 # target is own address with forward_to_gosa-tag pointing at myself -> forward to gosa
1211 if (not $done) {
1212 my $forward_to_gosa = @{$msg_hash->{'forward_to_gosa'}}[0];
1213 my $gosa_at;
1214 my $gosa_session_id;
1215 if (($target eq $local_address) && (defined $forward_to_gosa)){
1216 my ($gosa_at, $gosa_session_id) = split(/,/, $forward_to_gosa);
1217 if ($gosa_at eq $local_address) {
1218 my $session_reference = $kernel->ID_id_to_session($gosa_session_id);
1219 if( defined $session_reference ) {
1220 $heap = $session_reference->get_heap();
1221 }
1222 if(exists $heap->{'client'}) {
1223 $msg = &encrypt_msg($msg, $GosaPackages_key);
1224 $heap->{'client'}->put($msg);
1225 &daemon_log("$session_id INFO: incoming '$header' message forwarded to GOsa", 5);
1226 }
1227 $done = 1;
1228 &daemon_log("$session_id DEBUG: target is own address with forward_to_gosa-tag pointing at myself -> forward to gosa", 7);
1229 }
1230 }
1232 }
1234 # target is a client address in foreign_clients -> forward to registration server
1235 if (not $done) {
1236 $sql = "SELECT * FROM $foreign_clients_tn WHERE (hostname='$target' OR macaddress LIKE '$target')";
1237 $res = $foreign_clients_db->select_dbentry($sql);
1238 if (keys(%$res) > 0) {
1239 my $hostname = $res->{1}->{'hostname'};
1240 my ($host_ip, $host_port) = split(/:/, $hostname);
1241 my $local_address = &get_local_ip_for_remote_ip($host_ip).":$server_port";
1242 my $regserver = $res->{1}->{'regserver'};
1243 my $sql = "SELECT * FROM $known_server_tn WHERE hostname='$regserver'";
1244 my $res = $known_server_db->select_dbentry($sql);
1245 if (keys(%$res) > 0) {
1246 my $regserver_key = $res->{1}->{'hostkey'};
1247 $msg =~ s/<source>GOSA<\/source>/<source>$local_address<\/source>/;
1248 $msg =~ s/<target>$target<\/target>/<target>$hostname<\/target>/;
1249 if ($source eq "GOSA") {
1250 $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1251 }
1252 &send_msg_to_target($msg, $regserver, $regserver_key, $header, $session_id);
1253 }
1254 $done = 1;
1255 &daemon_log("$session_id DEBUG: target is a client address in foreign_clients -> forward to registration server", 7);
1256 } else {
1257 $not_found_in_foreign_clients_db = 1;
1258 }
1259 }
1261 # target is a server address -> forward to server
1262 if (not $done) {
1263 $sql = "SELECT * FROM $known_server_tn WHERE (hostname='$target' OR macaddress LIKE '$target')";
1264 $res = $known_server_db->select_dbentry($sql);
1265 if (keys(%$res) > 0) {
1266 my $hostkey = $res->{1}->{'hostkey'};
1268 if ($source eq "GOSA") {
1269 $msg =~ s/<source>GOSA<\/source>/<source>$local_address<\/source>/;
1270 $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1272 }
1274 &send_msg_to_target($msg, $target, $hostkey, $header, $session_id);
1275 $done = 1;
1276 &daemon_log("$session_id DEBUG: target is a server address -> forward to server", 7);
1277 } else {
1278 $not_found_in_known_server_db = 1;
1279 }
1280 }
1283 # target is not in foreign_clients_db, known_server_db or known_clients_db, maybe it is a complete new one -> process here
1284 if ( $not_found_in_foreign_clients_db
1285 && $not_found_in_known_server_db
1286 && $not_found_in_known_clients_db) {
1287 if ($header =~ /^gosa_/ || $header =~ /^job_/) {
1288 $module = "GosaPackages";
1289 }
1292 my $res = $incoming_db->add_dbentry( {table=>$incoming_tn,
1293 primkey=>[],
1294 headertag=>$header,
1295 targettag=>$target,
1296 xmlmessage=>&encode_base64($msg),
1297 timestamp=>&get_time,
1298 module=>$module,
1299 sessionid=>$session_id,
1300 } );
1301 $done = 1;
1302 &daemon_log("$session_id DEBUG: target is not in foreign_clients_db, known_server_db or known_clients_db, maybe it is a complete new one -> process here", 7);
1303 }
1306 if (not $done) {
1307 daemon_log("$session_id ERROR: do not know what to do with this message: $msg", 1);
1308 if ($source eq "GOSA") {
1309 my %data = ('error_msg' => &encode_base64($msg), 'error_string' => "Do not know what to do with this message!");
1310 my $error_msg = &build_msg("error", $local_address, "GOSA", \%data );
1312 my $session_reference = $kernel->ID_id_to_session($session_id);
1313 if( defined $session_reference ) {
1314 $heap = $session_reference->get_heap();
1315 }
1316 if(exists $heap->{'client'}) {
1317 $error_msg = &encrypt_msg($error_msg, $GosaPackages_key);
1318 $heap->{'client'}->put($error_msg);
1319 }
1320 }
1321 }
1323 }
1325 return;
1326 }
1329 sub next_task {
1330 my ($session, $heap, $task) = @_[SESSION, HEAP, ARG0];
1331 my $running_task = POE::Wheel::Run->new(
1332 Program => sub { process_task($session, $heap, $task) },
1333 StdioFilter => POE::Filter::Reference->new(),
1334 StdoutEvent => "task_result",
1335 StderrEvent => "task_debug",
1336 CloseEvent => "task_done",
1337 );
1338 $heap->{task}->{ $running_task->ID } = $running_task;
1339 }
1341 sub handle_task_result {
1342 my ($kernel, $heap, $result) = @_[KERNEL, HEAP, ARG0];
1343 my $client_answer = $result->{'answer'};
1344 if( $client_answer =~ s/session_id=(\d+)$// ) {
1345 my $session_id = $1;
1346 if( defined $session_id ) {
1347 my $session_reference = $kernel->ID_id_to_session($session_id);
1348 if( defined $session_reference ) {
1349 $heap = $session_reference->get_heap();
1350 }
1351 }
1353 if(exists $heap->{'client'}) {
1354 $heap->{'client'}->put($client_answer);
1355 }
1356 }
1357 $kernel->sig(CHLD => "child_reap");
1358 }
1360 sub handle_task_debug {
1361 my $result = $_[ARG0];
1362 print STDERR "$result\n";
1363 }
1365 sub handle_task_done {
1366 my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
1367 delete $heap->{task}->{$task_id};
1368 }
1370 sub process_task {
1371 no strict "refs";
1372 #CHECK: Not @_[...]?
1373 my ($session, $heap, $task) = @_;
1374 my $error = 0;
1375 my $answer_l;
1376 my ($answer_header, @answer_target_l, $answer_source);
1377 my $client_answer = "";
1379 # prepare all variables needed to process message
1380 #my $msg = $task->{'xmlmessage'};
1381 my $msg = &decode_base64($task->{'xmlmessage'});
1382 my $incoming_id = $task->{'id'};
1383 my $module = $task->{'module'};
1384 my $header = $task->{'headertag'};
1385 my $session_id = $task->{'sessionid'};
1386 my $msg_hash = $xml->XMLin($msg, ForceArray=>1);
1387 my $source = @{$msg_hash->{'source'}}[0];
1389 # set timestamp of incoming client uptodate, so client will not
1390 # be deleted from known_clients because of expiration
1391 my $act_time = &get_time();
1392 my $sql = "UPDATE $known_clients_tn SET timestamp='$act_time' WHERE hostname='$source'";
1393 my $res = $known_clients_db->exec_statement($sql);
1395 ######################
1396 # process incoming msg
1397 if( $error == 0) {
1398 daemon_log("$session_id INFO: Incoming msg (session_id=$session_id) with header '".@{$msg_hash->{'header'}}[0]."'", 5);
1399 daemon_log("$session_id DEBUG: Processing module ".$module, 7);
1400 $answer_l = &{ $module."::process_incoming_msg" }($msg, $msg_hash, $session_id);
1402 if ( 0 < @{$answer_l} ) {
1403 my $answer_str = join("\n", @{$answer_l});
1404 while ($answer_str =~ /<header>(\w+)<\/header>/g) {
1405 daemon_log("$session_id INFO: got answer message with header '$1'", 5);
1406 }
1407 daemon_log("$session_id DEBUG: $module: got answer from module: \n".$answer_str,9);
1408 } else {
1409 daemon_log("$session_id DEBUG: $module: got no answer from module!" ,7);
1410 }
1412 }
1413 if( !$answer_l ) { $error++ };
1415 ########
1416 # answer
1417 if( $error == 0 ) {
1419 foreach my $answer ( @{$answer_l} ) {
1420 # check outgoing msg to xml validity
1421 my $answer_hash = &check_outgoing_xml_validity($answer, $session_id);
1422 if( not defined $answer_hash ) { next; }
1424 $answer_header = @{$answer_hash->{'header'}}[0];
1425 @answer_target_l = @{$answer_hash->{'target'}};
1426 $answer_source = @{$answer_hash->{'source'}}[0];
1428 # deliver msg to all targets
1429 foreach my $answer_target ( @answer_target_l ) {
1431 # targets of msg are all gosa-si-clients in known_clients_db
1432 if( $answer_target eq "*" ) {
1433 # answer is for all clients
1434 my $sql_statement= "SELECT * FROM known_clients";
1435 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1436 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1437 my $host_name = $hit->{hostname};
1438 my $host_key = $hit->{hostkey};
1439 my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1440 &update_jobdb_status_for_send_msgs($session_id, $answer, $error);
1441 }
1442 }
1444 # targets of msg are all gosa-si-server in known_server_db
1445 elsif( $answer_target eq "KNOWN_SERVER" ) {
1446 # answer is for all server in known_server
1447 my $sql_statement= "SELECT * FROM $known_server_tn";
1448 my $query_res = $known_server_db->select_dbentry( $sql_statement );
1449 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1450 my $host_name = $hit->{hostname};
1451 my $host_key = $hit->{hostkey};
1452 $answer =~ s/<target>\S+<\/target>/<target>$host_name<\/target>/g;
1453 my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1454 &update_jobdb_status_for_send_msgs($session_id, $answer, $error);
1455 }
1456 }
1458 # target of msg is GOsa
1459 elsif( $answer_target eq "GOSA" ) {
1460 my $session_id = ($1) if $answer =~ /<session_id>(\d+?)<\/session_id>/;
1461 my $add_on = "";
1462 if( defined $session_id ) {
1463 $add_on = ".session_id=$session_id";
1464 }
1465 # answer is for GOSA and has to returned to connected client
1466 my $gosa_answer = &encrypt_msg($answer, $GosaPackages_key);
1467 $client_answer = $gosa_answer.$add_on;
1468 }
1470 # target of msg is job queue at this host
1471 elsif( $answer_target eq "JOBDB") {
1472 $answer =~ /<header>(\S+)<\/header>/;
1473 my $header;
1474 if( defined $1 ) { $header = $1; }
1475 my $error = &send_msg_to_target($answer, $server_address, $GosaPackages_key, $header, $session_id);
1476 &update_jobdb_status_for_send_msgs($session_id, $answer, $error);
1477 }
1479 # Target of msg is a mac address
1480 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 ) {
1481 daemon_log("$session_id INFO: target is mac address '$answer_target', looking for host in known_clients and foreign_clients", 5);
1482 my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$answer_target'";
1483 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1484 my $found_ip_flag = 0;
1485 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1486 my $host_name = $hit->{hostname};
1487 my $host_key = $hit->{hostkey};
1488 $answer =~ s/$answer_target/$host_name/g;
1489 daemon_log("$session_id INFO: found host '$host_name', associated to '$answer_target'", 5);
1490 my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1491 &update_jobdb_status_for_send_msgs($session_id, $answer, $error);
1492 $found_ip_flag++ ;
1493 }
1494 if ($found_ip_flag == 0) {
1495 my $sql = "SELECT * FROM $foreign_clients_tn WHERE macaddress LIKE '$answer_target'";
1496 my $res = $foreign_clients_db->select_dbentry($sql);
1497 while( my ($hit_num, $hit) = each %{ $res } ) {
1498 my $host_name = $hit->{hostname};
1499 my $reg_server = $hit->{regserver};
1500 daemon_log("$session_id INFO: found host '$host_name' with mac '$answer_target', registered at '$reg_server'", 5);
1502 # Fetch key for reg_server
1503 my $reg_server_key;
1504 my $sql = "SELECT * FROM $known_server_tn WHERE hostname='$reg_server'";
1505 my $res = $known_server_db->select_dbentry($sql);
1506 if (exists $res->{1}) {
1507 $reg_server_key = $res->{1}->{'hostkey'};
1508 } else {
1509 daemon_log("$session_id ERROR: cannot find hostkey for '$host_name' in '$known_server_tn'", 1);
1510 daemon_log("$session_id ERROR: unable to forward answer to correct registration server, processing is aborted!", 1);
1511 $reg_server_key = undef;
1512 }
1514 # Send answer to server where client is registered
1515 if (defined $reg_server_key) {
1516 $answer =~ s/$answer_target/$host_name/g;
1517 my $error = &send_msg_to_target($answer, $reg_server, $reg_server_key, $answer_header, $session_id);
1518 &update_jobdb_status_for_send_msgs($session_id, $answer, $error);
1519 $found_ip_flag++ ;
1520 }
1521 }
1522 }
1523 if( $found_ip_flag == 0) {
1524 daemon_log("$session_id WARNING: no host found in known_clients or foreign_clients with mac address '$answer_target'", 3);
1525 &reactivate_job_with_delay($session_id, $answer_target, $answer_header, 30);
1526 }
1528 # Answer is for one specific host
1529 } else {
1530 # get encrypt_key
1531 my $encrypt_key = &get_encrypt_key($answer_target);
1532 if( not defined $encrypt_key ) {
1533 # unknown target
1534 daemon_log("$session_id WARNING: unknown target '$answer_target'", 3);
1535 next;
1536 }
1537 my $error = &send_msg_to_target($answer, $answer_target, $encrypt_key, $answer_header,$session_id);
1538 &update_jobdb_status_for_send_msgs($session_id,$answer, $error);
1539 }
1540 }
1541 }
1542 }
1544 my $filter = POE::Filter::Reference->new();
1545 my %result = (
1546 status => "seems ok to me",
1547 answer => $client_answer,
1548 );
1550 my $output = $filter->put( [ \%result ] );
1551 print @$output;
1554 }
1556 sub session_start {
1557 my ($kernel) = $_[KERNEL];
1558 $global_kernel = $kernel;
1559 $kernel->yield('register_at_foreign_servers');
1560 $kernel->yield('create_fai_server_db', $fai_server_tn );
1561 $kernel->yield('create_fai_release_db', $fai_release_tn );
1562 $kernel->yield('watch_for_next_tasks');
1563 $kernel->sig(USR1 => "sig_handler");
1564 $kernel->sig(USR2 => "recreate_packages_db");
1565 $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1566 $kernel->delay_set('watch_for_done_jobs', $job_queue_loop_delay);
1567 $kernel->delay_set('watch_for_modified_jobs', $modified_jobs_loop_delay);
1568 $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay);
1569 $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay);
1570 $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay);
1571 $kernel->delay_set('watch_for_old_known_clients', $job_queue_loop_delay);
1573 # Start opsi check
1574 if ($opsi_enabled eq "true") {
1575 $kernel->delay_set('watch_for_opsi_jobs', $job_queue_opsi_delay);
1576 }
1578 }
1581 sub watch_for_done_jobs {
1582 #CHECK: $heap for what?
1583 my ($kernel,$heap) = @_[KERNEL, HEAP];
1585 my $sql_statement = "SELECT * FROM ".$job_queue_tn." WHERE ((status='done') AND (modified='0'))";
1586 my $res = $job_db->select_dbentry( $sql_statement );
1588 while( my ($id, $hit) = each %{$res} ) {
1589 my $jobdb_id = $hit->{id};
1590 my $sql_statement = "DELETE FROM $job_queue_tn WHERE id=$jobdb_id";
1591 my $res = $job_db->del_dbentry($sql_statement);
1592 }
1594 $kernel->delay_set('watch_for_done_jobs',$job_queue_loop_delay);
1595 }
1598 sub watch_for_opsi_jobs {
1599 my ($kernel) = $_[KERNEL];
1601 # This is not very nice to look for opsi install jobs, but headertag has to be trigger_action_reinstall. The only way to identify a
1602 # opsi install job is to parse the xml message. There is still the correct header.
1603 my $sql_statement = "SELECT * FROM ".$job_queue_tn." WHERE ((xmlmessage LIKE '%opsi_install_client</header>%') AND (status='processing') AND (siserver='localhost'))";
1604 my $res = $job_db->select_dbentry( $sql_statement );
1606 # Ask OPSI for an update of the running jobs
1607 while (my ($id, $hit) = each %$res ) {
1608 # Determine current parameters of the job
1609 my $hostId = $hit->{'plainname'};
1610 my $macaddress = $hit->{'macaddress'};
1611 my $progress = $hit->{'progress'};
1613 my $result= {};
1615 # For hosts, only return the products that are or get installed
1616 my $callobj;
1617 $callobj = {
1618 method => 'getProductStates_hash',
1619 params => [ $hostId ],
1620 id => 1,
1621 };
1623 my $hres = $opsi_client->call($opsi_url, $callobj);
1624 #my ($hres_err, $hres_err_string) = &check_opsi_res($hres);
1625 if (not &check_opsi_res($hres)) {
1626 my $htmp= $hres->result->{$hostId};
1628 # Check state != not_installed or action == setup -> load and add
1629 my $products= 0;
1630 my $installed= 0;
1631 my $installing = 0;
1632 my $error= 0;
1633 my @installed_list;
1634 my @error_list;
1635 my $act_status = "none";
1636 foreach my $product (@{$htmp}){
1638 if ($product->{'installationStatus'} ne "not_installed" or
1639 $product->{'actionRequest'} eq "setup"){
1641 # Increase number of products for this host
1642 $products++;
1644 if ($product->{'installationStatus'} eq "failed"){
1645 $result->{$product->{'productId'}}= "error";
1646 unshift(@error_list, $product->{'productId'});
1647 $error++;
1648 }
1649 if ($product->{'installationStatus'} eq "installed" && $product->{'actionRequest'} eq "none"){
1650 $result->{$product->{'productId'}}= "installed";
1651 unshift(@installed_list, $product->{'productId'});
1652 $installed++;
1653 }
1654 if ($product->{'installationStatus'} eq "installing"){
1655 $result->{$product->{'productId'}}= "installing";
1656 $installing++;
1657 $act_status = "installing - ".$product->{'productId'};
1658 }
1659 }
1660 }
1662 # Estimate "rough" progress, avoid division by zero
1663 if ($products == 0) {
1664 $result->{'progress'}= 0;
1665 } else {
1666 $result->{'progress'}= int($installed * 100 / $products);
1667 }
1669 # Set updates in job queue
1670 if ((not $error) && (not $installing) && ($installed)) {
1671 $act_status = "installed - ".join(", ", @installed_list);
1672 }
1673 if ($error) {
1674 $act_status = "error - ".join(", ", @error_list);
1675 }
1676 if ($progress ne $result->{'progress'} ) {
1677 # Updating progress and result
1678 my $update_statement = "UPDATE $job_queue_tn SET modified='1', progress='".$result->{'progress'}."', result='$act_status' WHERE macaddress='$macaddress' AND siserver='localhost'";
1679 my $update_res = $job_db->update_dbentry($update_statement);
1680 }
1681 if ($progress eq 100) {
1682 # Updateing status
1683 my $done_statement = "UPDATE $job_queue_tn SET modified='1', ";
1684 if ($error) {
1685 $done_statement .= "status='error'";
1686 } else {
1687 $done_statement .= "status='done'";
1688 }
1689 $done_statement .= " WHERE macaddress='$macaddress' AND siserver='localhost'";
1690 my $done_res = $job_db->update_dbentry($done_statement);
1691 }
1694 }
1695 }
1697 $kernel->delay_set('watch_for_opsi_jobs', $job_queue_opsi_delay);
1698 }
1701 # If a job got an update or was modified anyway, send to all other si-server an update message of this jobs.
1702 sub watch_for_modified_jobs {
1703 my ($kernel,$heap) = @_[KERNEL, HEAP];
1705 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE (modified='1')";
1706 my $res = $job_db->select_dbentry( $sql_statement );
1708 # if db contains no jobs which should be update, do nothing
1709 if (keys %$res != 0) {
1711 if ($job_synchronization eq "true") {
1712 # make out of the db result a gosa-si message
1713 my $update_msg = &db_res2si_msg ($res, "foreign_job_updates", "KNOWN_SERVER", "MY_LOCAL_ADDRESS");
1715 # update all other SI-server
1716 &inform_all_other_si_server($update_msg);
1717 }
1719 # set jobs all jobs to modified = 0, wait until the next modification for updates of other si-server
1720 $sql_statement = "UPDATE $job_queue_tn SET modified='0' ";
1721 $res = $job_db->update_dbentry($sql_statement);
1722 }
1724 $kernel->delay_set('watch_for_modified_jobs', $modified_jobs_loop_delay);
1725 }
1728 sub watch_for_new_jobs {
1729 if($watch_for_new_jobs_in_progress == 0) {
1730 $watch_for_new_jobs_in_progress = 1;
1731 my ($kernel,$heap) = @_[KERNEL, HEAP];
1733 # check gosa job quaeue for jobs with executable timestamp
1734 my $timestamp = &get_time();
1735 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE status='waiting' AND (CAST (timestamp AS INTEGER)) < $timestamp ORDER BY timestamp";
1736 my $res = $job_db->exec_statement( $sql_statement );
1738 # Merge all new jobs that would do the same actions
1739 my @drops;
1740 my $hits;
1741 foreach my $hit (reverse @{$res} ) {
1742 my $macaddress= lc @{$hit}[8];
1743 my $headertag= @{$hit}[5];
1744 if(
1745 defined($hits->{$macaddress}) &&
1746 defined($hits->{$macaddress}->{$headertag}) &&
1747 defined($hits->{$macaddress}->{$headertag}[0])
1748 ) {
1749 push @drops, "DELETE FROM $job_queue_tn WHERE id = $hits->{$macaddress}->{$headertag}[0]";
1750 }
1751 $hits->{$macaddress}->{$headertag}= $hit;
1752 }
1754 # Delete new jobs with a matching job in state 'processing'
1755 foreach my $macaddress (keys %{$hits}) {
1756 foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1757 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1758 if(defined($jobdb_id)) {
1759 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND headertag='$jobdb_headertag' AND status='processing'";
1760 my $res = $job_db->exec_statement( $sql_statement );
1761 foreach my $hit (@{$res}) {
1762 push @drops, "DELETE FROM $job_queue_tn WHERE id=$jobdb_id";
1763 }
1764 } else {
1765 daemon_log("J ERROR: Job without id exists for macaddress $macaddress!", 1);
1766 }
1767 }
1768 }
1770 # Commit deletion
1771 $job_db->exec_statementlist(\@drops);
1773 # Look for new jobs that could be executed
1774 foreach my $macaddress (keys %{$hits}) {
1776 # Look if there is an executing job
1777 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND status='processing'";
1778 my $res = $job_db->exec_statement( $sql_statement );
1780 # Skip new jobs for host if there is a processing job
1781 if(defined($res) and defined @{$res}[0]) {
1782 # Prevent race condition if there is a trigger_activate job waiting and a goto-activation job processing
1783 my $row = @{$res}[0] if (ref $res eq 'ARRAY');
1784 if(@{$row}[5] eq 'trigger_action_reinstall') {
1785 my $sql_statement_2 = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND status='waiting' AND headertag = 'trigger_activate_new'";
1786 my $res_2 = $job_db->exec_statement( $sql_statement_2 );
1787 if(defined($res_2) and defined @{$res_2}[0]) {
1788 # Set status from goto-activation to 'waiting' and update timestamp
1789 $job_db->exec_statement("UPDATE $job_queue_tn SET status='waiting' WHERE macaddress LIKE '$macaddress' AND headertag = 'trigger_action_reinstall'");
1790 $job_db->exec_statement("UPDATE $job_queue_tn SET timestamp='".&calc_timestamp(&get_time(), 'plus', 30)."' WHERE macaddress LIKE '$macaddress' AND headertag = 'trigger_action_reinstall'");
1791 }
1792 }
1793 next;
1794 }
1796 foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1797 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1798 if(defined($jobdb_id)) {
1799 my $job_msg = @{$hits->{$macaddress}->{$jobdb_headertag}}[7];
1801 daemon_log("J DEBUG: its time to execute $job_msg", 7);
1802 my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$macaddress'";
1803 my $res_hash = $known_clients_db->select_dbentry( $sql_statement );
1805 # expect macaddress is unique!!!!!!
1806 my $target = $res_hash->{1}->{hostname};
1808 # change header
1809 $job_msg =~ s/<header>job_/<header>gosa_/;
1811 # add sqlite_id
1812 $job_msg =~ s/<\/xml>$/<jobdb_id>$jobdb_id<\/jobdb_id><\/xml>/;
1814 $job_msg =~ /<header>(\S+)<\/header>/;
1815 my $header = $1 ;
1816 my $func_error = &send_msg_to_target($job_msg, $server_address, $GosaPackages_key, $header, "J");
1818 # update status in job queue to ...
1819 # ... 'processing', for jobs: 'reinstall', 'update'
1820 if (($header =~ /gosa_trigger_action_reinstall/)
1821 || ($header =~ /gosa_trigger_activate_new/)
1822 || ($header =~ /gosa_trigger_action_update/)) {
1823 my $sql_statement = "UPDATE $job_queue_tn SET status='processing' WHERE id=$jobdb_id";
1824 my $dbres = $job_db->update_dbentry($sql_statement);
1825 }
1827 # ... 'done', for all other jobs, they are no longer needed in the jobqueue
1828 else {
1829 my $sql_statement = "UPDATE $job_queue_tn SET status='done' WHERE id=$jobdb_id";
1830 my $dbres = $job_db->update_dbentry($sql_statement);
1831 }
1833 # We don't want parallel processing
1834 last;
1835 }
1836 }
1837 }
1839 $watch_for_new_jobs_in_progress = 0;
1840 $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1841 }
1842 }
1845 sub watch_for_new_messages {
1846 my ($kernel,$heap) = @_[KERNEL, HEAP];
1847 my @coll_user_msg; # collection list of outgoing messages
1849 # check messaging_db for new incoming messages with executable timestamp
1850 my $timestamp = &get_time();
1851 my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( (CAST(timestamp AS INTEGER))<$timestamp AND flag='n' AND direction='in' )";
1852 my $res = $messaging_db->exec_statement( $sql_statement );
1853 foreach my $hit (@{$res}) {
1855 # create outgoing messages
1856 my $message_to = @{$hit}[3];
1857 # translate message_to to plain login name
1858 my @message_to_l = split(/,/, $message_to);
1859 my %receiver_h;
1860 foreach my $receiver (@message_to_l) {
1861 if ($receiver =~ /^u_([\s\S]*)$/) {
1862 $receiver_h{$1} = 0;
1863 } elsif ($receiver =~ /^g_([\s\S]*)$/) {
1864 my $group_name = $1;
1865 # fetch all group members from ldap and add them to receiver hash
1866 my $ldap_handle = &get_ldap_handle();
1867 if (defined $ldap_handle) {
1868 my $mesg = $ldap_handle->search(
1869 base => $ldap_base,
1870 scope => 'sub',
1871 attrs => ['memberUid'],
1872 filter => "cn=$group_name",
1873 );
1874 if ($mesg->count) {
1875 my @entries = $mesg->entries;
1876 foreach my $entry (@entries) {
1877 my @receivers= $entry->get_value("memberUid");
1878 foreach my $receiver (@receivers) {
1879 $receiver_h{$receiver} = 0;
1880 }
1881 }
1882 }
1883 # translating errors ?
1884 if ($mesg->code) {
1885 daemon_log("M ERROR: unable to translate group '$group_name' to user list for message delivery: $mesg->error", 1);
1886 }
1887 # ldap handle error ?
1888 } else {
1889 daemon_log("M ERROR: unable to translate group '$group_name' to user list for message delivery: no ldap handle available", 1);
1890 }
1891 } else {
1892 my $sbjct = &encode_base64(@{$hit}[1]);
1893 my $msg = &encode_base64(@{$hit}[7]);
1894 &daemon_log("M WARNING: unknown receiver '$receiver' for a user-message '$sbjct - $msg'", 3);
1895 }
1896 }
1897 my @receiver_l = keys(%receiver_h);
1899 my $message_id = @{$hit}[0];
1901 #add each outgoing msg to messaging_db
1902 my $receiver;
1903 foreach $receiver (@receiver_l) {
1904 my $sql_statement = "INSERT INTO $messaging_tn (id, subject, message_from, message_to, flag, direction, delivery_time, message, timestamp) ".
1905 "VALUES ('".
1906 $message_id."', '". # id
1907 @{$hit}[1]."', '". # subject
1908 @{$hit}[2]."', '". # message_from
1909 $receiver."', '". # message_to
1910 "none"."', '". # flag
1911 "out"."', '". # direction
1912 @{$hit}[6]."', '". # delivery_time
1913 @{$hit}[7]."', '". # message
1914 $timestamp."'". # timestamp
1915 ")";
1916 &daemon_log("M DEBUG: $sql_statement", 1);
1917 my $res = $messaging_db->exec_statement($sql_statement);
1918 &daemon_log("M INFO: message '".@{$hit}[0]."' is prepared for delivery to receiver '$receiver'", 5);
1919 }
1921 # set incoming message to flag d=deliverd
1922 $sql_statement = "UPDATE $messaging_tn SET flag='p' WHERE id='$message_id'";
1923 &daemon_log("M DEBUG: $sql_statement", 7);
1924 $res = $messaging_db->update_dbentry($sql_statement);
1925 &daemon_log("M INFO: message '$message_id' is set to flag 'p' (processed)", 5);
1926 }
1928 $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay);
1929 return;
1930 }
1932 sub watch_for_delivery_messages {
1933 my ($kernel, $heap) = @_[KERNEL, HEAP];
1935 # select outgoing messages
1936 my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( flag='p' AND direction='out' )";
1937 #&daemon_log("0 DEBUG: $sql", 7);
1938 my $res = $messaging_db->exec_statement( $sql_statement );
1940 # build out msg for each usr
1941 foreach my $hit (@{$res}) {
1942 my $receiver = @{$hit}[3];
1943 my $msg_id = @{$hit}[0];
1944 my $subject = @{$hit}[1];
1945 my $message = @{$hit}[7];
1947 # resolve usr -> host where usr is logged in
1948 my $sql = "SELECT * FROM $login_users_tn WHERE (user='$receiver')";
1949 #&daemon_log("0 DEBUG: $sql", 7);
1950 my $res = $login_users_db->exec_statement($sql);
1952 # reciver is logged in nowhere
1953 if (not ref(@$res[0]) eq "ARRAY") { next; }
1955 my $send_succeed = 0;
1956 foreach my $hit (@$res) {
1957 my $receiver_host = @$hit[0];
1958 my $delivered2host = 0;
1959 &daemon_log("M DEBUG: user '$receiver' is logged in at host '$receiver_host'", 7);
1961 # Looking for host in know_clients_db
1962 my $sql = "SELECT * FROM $known_clients_tn WHERE (hostname='$receiver_host')";
1963 my $res = $known_clients_db->exec_statement($sql);
1965 # Host is known in known_clients_db
1966 if (ref(@$res[0]) eq "ARRAY") {
1967 my $receiver_key = @{@{$res}[0]}[2];
1968 my %data = ('subject' => $subject, 'message' => $message, 'usr' => $receiver);
1969 my $out_msg = &build_msg("usr_msg", $server_address, $receiver_host, \%data );
1970 my $error = &send_msg_to_target($out_msg, $receiver_host, $receiver_key, "usr_msg", 0);
1971 if ($error == 0 ) {
1972 $send_succeed++ ;
1973 $delivered2host++ ;
1974 &daemon_log("M DEBUG: send message for user '$receiver' to host '$receiver_host'", 7);
1975 } else {
1976 &daemon_log("M DEBUG: cannot send message for user '$receiver' to host '$receiver_host'", 7);
1977 }
1978 }
1980 # Message already send, do not need to do anything more, otherwise ...
1981 if ($delivered2host) { next;}
1983 # ...looking for host in foreign_clients_db
1984 $sql = "SELECT * FROM $foreign_clients_tn WHERE (hostname='$receiver_host')";
1985 $res = $foreign_clients_db->exec_statement($sql);
1987 # Host is known in foreign_clients_db
1988 if (ref(@$res[0]) eq "ARRAY") {
1989 my $registration_server = @{@{$res}[0]}[2];
1991 # Fetch encryption key for registration server
1992 my $sql = "SELECT * FROM $known_server_tn WHERE (hostname='$registration_server')";
1993 my $res = $known_server_db->exec_statement($sql);
1994 if (ref(@$res[0]) eq "ARRAY") {
1995 my $registration_server_key = @{@{$res}[0]}[3];
1996 my %data = ('subject' => $subject, 'message' => $message, 'usr' => $receiver);
1997 my $out_msg = &build_msg("usr_msg", $server_address, $receiver_host, \%data );
1998 my $error = &send_msg_to_target($out_msg, $registration_server, $registration_server_key, "usr_msg", 0);
1999 if ($error == 0 ) {
2000 $send_succeed++ ;
2001 $delivered2host++ ;
2002 &daemon_log("M DEBUG: send message for user '$receiver' to server '$registration_server'", 7);
2003 } else {
2004 &daemon_log("M ERROR: cannot send message for user '$receiver' to server '$registration_server'", 1);
2005 }
2007 } else {
2008 &daemon_log("M ERROR: host '$receiver_host' is reported to be ".
2009 "registrated at server '$registration_server', ".
2010 "but no data available in known_server_db ", 1);
2011 }
2012 }
2014 if (not $delivered2host) {
2015 &daemon_log("M ERROR: unable to send user message to host '$receiver_host'", 1);
2016 }
2017 }
2019 if ($send_succeed) {
2020 # set outgoing msg at db to deliverd
2021 my $sql = "UPDATE $messaging_tn SET flag='d' WHERE (id='$msg_id' AND direction='out' AND message_to='$receiver')";
2022 my $res = $messaging_db->exec_statement($sql);
2023 &daemon_log("M INFO: send message for user '$receiver' to logged in hosts", 5);
2024 } else {
2025 &daemon_log("M WARNING: failed to deliver message for user '$receiver'", 3);
2026 }
2027 }
2029 $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay);
2030 return;
2031 }
2034 sub watch_for_done_messages {
2035 my ($kernel,$heap) = @_[KERNEL, HEAP];
2037 my $sql = "SELECT * FROM $messaging_tn WHERE (flag='p' AND direction='in')";
2038 #&daemon_log("0 DEBUG: $sql", 7);
2039 my $res = $messaging_db->exec_statement($sql);
2041 foreach my $hit (@{$res}) {
2042 my $msg_id = @{$hit}[0];
2044 my $sql = "SELECT * FROM $messaging_tn WHERE (id='$msg_id' AND direction='out' AND (NOT flag='s'))";
2045 #&daemon_log("0 DEBUG: $sql", 7);
2046 my $res = $messaging_db->exec_statement($sql);
2048 # not all usr msgs have been seen till now
2049 if ( ref(@$res[0]) eq "ARRAY") { next; }
2051 $sql = "DELETE FROM $messaging_tn WHERE (id='$msg_id')";
2052 #&daemon_log("0 DEBUG: $sql", 7);
2053 $res = $messaging_db->exec_statement($sql);
2055 }
2057 $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay);
2058 return;
2059 }
2062 sub watch_for_old_known_clients {
2063 my ($kernel,$heap) = @_[KERNEL, HEAP];
2065 my $sql_statement = "SELECT * FROM $known_clients_tn";
2066 my $res = $known_clients_db->select_dbentry( $sql_statement );
2068 my $act_time = int(&get_time());
2070 while ( my ($hit_num, $hit) = each %$res) {
2071 my $expired_timestamp = int($hit->{'timestamp'});
2072 $expired_timestamp =~ /(\d{4})(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)/;
2073 my $dt = DateTime->new( year => $1,
2074 month => $2,
2075 day => $3,
2076 hour => $4,
2077 minute => $5,
2078 second => $6,
2079 );
2081 $dt->add( seconds => 2 * int($hit->{'keylifetime'}) );
2082 $expired_timestamp = $dt->ymd('').$dt->hms('');
2083 if ($act_time > $expired_timestamp) {
2084 my $hostname = $hit->{'hostname'};
2085 my $del_sql = "DELETE FROM $known_clients_tn WHERE hostname='$hostname'";
2086 my $del_res = $known_clients_db->exec_statement($del_sql);
2088 &main::daemon_log("0 INFO: timestamp '".$hit->{'timestamp'}."' of client '$hostname' is expired('$expired_timestamp'), client will be deleted from known_clients_db", 5);
2089 }
2091 }
2093 $kernel->delay_set('watch_for_old_known_clients', $job_queue_loop_delay);
2094 }
2097 sub watch_for_next_tasks {
2098 my ($kernel,$heap) = @_[KERNEL, HEAP];
2100 my $sql = "SELECT * FROM $incoming_tn";
2101 my $res = $incoming_db->select_dbentry($sql);
2103 while ( my ($hit_num, $hit) = each %$res) {
2104 my $headertag = $hit->{'headertag'};
2105 if ($headertag =~ /^answer_(\d+)/) {
2106 # do not start processing, this message is for a still running POE::Wheel
2107 next;
2108 }
2109 my $message_id = $hit->{'id'};
2110 $kernel->yield('next_task', $hit);
2112 my $sql = "DELETE FROM $incoming_tn WHERE id=$message_id";
2113 my $res = $incoming_db->exec_statement($sql);
2114 }
2116 $kernel->delay_set('watch_for_next_tasks', 0.1);
2117 }
2120 sub get_ldap_handle {
2121 my ($session_id) = @_;
2122 my $heap;
2123 my $ldap_handle;
2125 if (not defined $session_id ) { $session_id = 0 };
2126 if ($session_id =~ /[^0-9]*/) { $session_id = 0 };
2128 if ($session_id == 0) {
2129 daemon_log("$session_id DEBUG: get_ldap_handle invoked without a session_id, create a new ldap_handle", 7);
2130 $ldap_handle = Net::LDAP->new( $ldap_uri );
2131 if (defined $ldap_handle) {
2132 $ldap_handle->bind($ldap_admin_dn, password => $ldap_admin_password) or daemon_log("$session_id ERROR: Bind to LDAP $ldap_uri as $ldap_admin_dn failed!");
2133 } else {
2134 daemon_log("$session_id ERROR: creation of a new LDAP handle failed (ldap_uri '$ldap_uri')");
2135 }
2137 } else {
2138 my $session_reference = $global_kernel->ID_id_to_session($session_id);
2139 if( defined $session_reference ) {
2140 $heap = $session_reference->get_heap();
2141 }
2143 if (not defined $heap) {
2144 daemon_log("$session_id DEBUG: cannot get heap for session_id '$session_id'", 7);
2145 return;
2146 }
2148 # TODO: This "if" is nonsense, because it doesn't prove that the
2149 # used handle is still valid - or if we've to reconnect...
2150 #if (not exists $heap->{ldap_handle}) {
2151 $ldap_handle = Net::LDAP->new( $ldap_uri );
2152 $ldap_handle->bind($ldap_admin_dn, password => $ldap_admin_password) or daemon_log("$session_id ERROR: Bind to LDAP $ldap_uri as $ldap_admin_dn failed!");
2153 $heap->{ldap_handle} = $ldap_handle;
2154 #}
2155 }
2156 return $ldap_handle;
2157 }
2160 sub change_fai_state {
2161 my ($st, $targets, $session_id) = @_;
2162 $session_id = 0 if not defined $session_id;
2163 # Set FAI state to localboot
2164 my %mapActions= (
2165 reboot => '',
2166 update => 'softupdate',
2167 localboot => 'localboot',
2168 reinstall => 'install',
2169 rescan => '',
2170 wake => '',
2171 memcheck => 'memcheck',
2172 sysinfo => 'sysinfo',
2173 install => 'install',
2174 );
2176 # Return if this is unknown
2177 if (!exists $mapActions{ $st }){
2178 daemon_log("$session_id ERROR: unknown action '$st', can not translate ot FAIstate", 1);
2179 return;
2180 }
2182 my $state= $mapActions{ $st };
2184 my $ldap_handle = &get_ldap_handle($session_id);
2185 if( defined($ldap_handle) ) {
2187 # Build search filter for hosts
2188 my $search= "(&(objectClass=GOhard)";
2189 foreach (@{$targets}){
2190 $search.= "(macAddress=$_)";
2191 }
2192 $search.= ")";
2194 # If there's any host inside of the search string, procress them
2195 if (!($search =~ /macAddress/)){
2196 daemon_log("$session_id ERROR: no macAddress found in filter statement for LDAP search: '$search'", 1);
2197 return;
2198 }
2200 # Perform search for Unit Tag
2201 my $mesg = $ldap_handle->search(
2202 base => $ldap_base,
2203 scope => 'sub',
2204 attrs => ['dn', 'FAIstate', 'objectClass'],
2205 filter => "$search"
2206 );
2208 if ($mesg->count) {
2209 my @entries = $mesg->entries;
2210 if (0 == @entries) {
2211 daemon_log("$session_id ERROR: ldap search failed: ldap_base=$ldap_base, filter=$search", 1);
2212 }
2214 foreach my $entry (@entries) {
2215 # Only modify entry if it is not set to '$state'
2216 if ($entry->get_value("FAIstate") ne "$state"){
2217 daemon_log("$session_id INFO: Setting FAIstate to '$state' for ".$entry->dn, 5);
2218 my $result;
2219 my %tmp = map { $_ => 1 } $entry->get_value("objectClass");
2220 if (exists $tmp{'FAIobject'}){
2221 if ($state eq ''){
2222 $result= $ldap_handle->modify($entry->dn, changes => [
2223 delete => [ FAIstate => [] ] ]);
2224 } else {
2225 $result= $ldap_handle->modify($entry->dn, changes => [
2226 replace => [ FAIstate => $state ] ]);
2227 }
2228 } elsif ($state ne ''){
2229 $result= $ldap_handle->modify($entry->dn, changes => [
2230 add => [ objectClass => 'FAIobject' ],
2231 add => [ FAIstate => $state ] ]);
2232 }
2234 # Errors?
2235 if ($result->code){
2236 daemon_log("$session_id Error: Setting FAIstate to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
2237 }
2238 } else {
2239 daemon_log("$session_id DEBUG FAIstate at host '".$entry->dn."' already at state '$st'", 7);
2240 }
2241 }
2242 } else {
2243 daemon_log("$session_id ERROR: LDAP search failed: ldap_base=$ldap_base, filter=$search", 1);
2244 }
2246 # if no ldap handle defined
2247 } else {
2248 daemon_log("$session_id ERROR: no LDAP handle defined for update FAIstate", 1);
2249 }
2251 return;
2252 }
2255 sub change_goto_state {
2256 my ($st, $targets, $session_id) = @_;
2257 $session_id = 0 if not defined $session_id;
2259 # Switch on or off?
2260 my $state= $st eq 'active' ? 'active': 'locked';
2262 my $ldap_handle = &get_ldap_handle($session_id);
2263 if( defined($ldap_handle) ) {
2265 # Build search filter for hosts
2266 my $search= "(&(objectClass=GOhard)";
2267 foreach (@{$targets}){
2268 $search.= "(macAddress=$_)";
2269 }
2270 $search.= ")";
2272 # If there's any host inside of the search string, procress them
2273 if (!($search =~ /macAddress/)){
2274 return;
2275 }
2277 # Perform search for Unit Tag
2278 my $mesg = $ldap_handle->search(
2279 base => $ldap_base,
2280 scope => 'sub',
2281 attrs => ['dn', 'gotoMode'],
2282 filter => "$search"
2283 );
2285 if ($mesg->count) {
2286 my @entries = $mesg->entries;
2287 foreach my $entry (@entries) {
2289 # Only modify entry if it is not set to '$state'
2290 if ($entry->get_value("gotoMode") ne $state){
2292 daemon_log("$session_id INFO: Setting gotoMode to '$state' for ".$entry->dn, 5);
2293 my $result;
2294 $result= $ldap_handle->modify($entry->dn, changes => [
2295 replace => [ gotoMode => $state ] ]);
2297 # Errors?
2298 if ($result->code){
2299 &daemon_log("$session_id Error: Setting gotoMode to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
2300 }
2302 }
2303 }
2304 } else {
2305 daemon_log("$session_id ERROR: LDAP search failed in function change_goto_state: ldap_base=$ldap_base, filter=$search", 1);
2306 }
2308 }
2309 }
2312 sub run_recreate_packages_db {
2313 my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
2314 my $session_id = $session->ID;
2315 &main::daemon_log("$session_id INFO: Recreating FAI Packages DB ('$fai_release_tn', '$fai_server_tn', '$packages_list_tn')", 5);
2316 $kernel->yield('create_fai_release_db', $fai_release_tn);
2317 $kernel->yield('create_fai_server_db', $fai_server_tn);
2318 return;
2319 }
2322 sub run_create_fai_server_db {
2323 my ($kernel, $session, $heap, $table_name) = @_[KERNEL, SESSION, HEAP, ARG0];
2324 my $session_id = $session->ID;
2325 my $task = POE::Wheel::Run->new(
2326 Program => sub { &create_fai_server_db($table_name,$kernel, undef, $session_id) },
2327 StdoutEvent => "session_run_result",
2328 StderrEvent => "session_run_debug",
2329 CloseEvent => "session_run_done",
2330 );
2332 $heap->{task}->{ $task->ID } = $task;
2333 return;
2334 }
2337 sub create_fai_server_db {
2338 my ($table_name, $kernel, $dont_create_packages_list, $session_id) = @_;
2339 my $result;
2341 if (not defined $session_id) { $session_id = 0; }
2342 my $ldap_handle = &get_ldap_handle();
2343 if(defined($ldap_handle)) {
2344 daemon_log("$session_id INFO: create_fai_server_db: start", 5);
2345 my $mesg= $ldap_handle->search(
2346 base => $ldap_base,
2347 scope => 'sub',
2348 attrs => ['FAIrepository', 'gosaUnitTag'],
2349 filter => "(&(FAIrepository=*)(objectClass=FAIrepositoryServer))",
2350 );
2351 if($mesg->{'resultCode'} == 0 &&
2352 $mesg->count != 0) {
2353 foreach my $entry (@{$mesg->{entries}}) {
2354 if($entry->exists('FAIrepository')) {
2355 # Add an entry for each Repository configured for server
2356 foreach my $repo(@{$entry->get_value('FAIrepository', asref => 1)}) {
2357 my($tmp_url,$tmp_server,$tmp_release,$tmp_sections) = split(/\|/, $repo);
2358 my $tmp_tag= $entry->get_value('gosaUnitTag') || "";
2359 $result= $fai_server_db->add_dbentry( {
2360 table => $table_name,
2361 primkey => ['server', 'release', 'tag'],
2362 server => $tmp_url,
2363 release => $tmp_release,
2364 sections => $tmp_sections,
2365 tag => (length($tmp_tag)>0)?$tmp_tag:"",
2366 } );
2367 }
2368 }
2369 }
2370 }
2371 daemon_log("$session_id INFO: create_fai_server_db: finished", 5);
2373 # TODO: Find a way to post the 'create_packages_list_db' event
2374 if(not defined($dont_create_packages_list)) {
2375 &create_packages_list_db(undef, undef, $session_id);
2376 }
2377 }
2379 $ldap_handle->disconnect;
2380 return $result;
2381 }
2384 sub run_create_fai_release_db {
2385 my ($session, $heap, $table_name) = @_[SESSION, HEAP, ARG0];
2386 my $session_id = $session->ID;
2387 my $task = POE::Wheel::Run->new(
2388 Program => sub { &create_fai_release_db($table_name, $session_id) },
2389 StdoutEvent => "session_run_result",
2390 StderrEvent => "session_run_debug",
2391 CloseEvent => "session_run_done",
2392 );
2394 $heap->{task}->{ $task->ID } = $task;
2395 return;
2396 }
2399 sub create_fai_release_db {
2400 my ($table_name, $session_id) = @_;
2401 my $result;
2403 # used for logging
2404 if (not defined $session_id) { $session_id = 0; }
2406 my $ldap_handle = &get_ldap_handle();
2407 if(defined($ldap_handle)) {
2408 daemon_log("$session_id INFO: create_fai_release_db: start",5);
2409 my $mesg= $ldap_handle->search(
2410 base => $ldap_base,
2411 scope => 'sub',
2412 attrs => [],
2413 filter => "(&(objectClass=organizationalUnit)(ou=fai))",
2414 );
2415 if($mesg->{'resultCode'} == 0 &&
2416 $mesg->count != 0) {
2417 # Walk through all possible FAI container ou's
2418 my @sql_list;
2419 my $timestamp= &get_time();
2420 foreach my $ou (@{$mesg->{entries}}) {
2421 my $tmp_classes= resolve_fai_classes($ou->dn, $ldap_handle, $session_id);
2422 if(defined($tmp_classes) && ref($tmp_classes) eq 'HASH') {
2423 my @tmp_array=get_fai_release_entries($tmp_classes);
2424 if(@tmp_array) {
2425 foreach my $entry (@tmp_array) {
2426 if(defined($entry) && ref($entry) eq 'HASH') {
2427 my $sql=
2428 "INSERT INTO $table_name "
2429 ."(timestamp, release, class, type, state) VALUES ("
2430 .$timestamp.","
2431 ."'".$entry->{'release'}."',"
2432 ."'".$entry->{'class'}."',"
2433 ."'".$entry->{'type'}."',"
2434 ."'".$entry->{'state'}."')";
2435 push @sql_list, $sql;
2436 }
2437 }
2438 }
2439 }
2440 }
2442 daemon_log("$session_id DEBUG: Inserting ".scalar @sql_list." entries to DB",8);
2443 if(@sql_list) {
2444 unshift @sql_list, "VACUUM";
2445 unshift @sql_list, "DELETE FROM $table_name";
2446 $fai_release_db->exec_statementlist(\@sql_list);
2447 }
2448 daemon_log("$session_id DEBUG: Done with inserting",7);
2449 }
2450 daemon_log("$session_id INFO: create_fai_release_db: finished",5);
2451 }
2452 $ldap_handle->disconnect;
2453 return $result;
2454 }
2456 sub get_fai_types {
2457 my $tmp_classes = shift || return undef;
2458 my @result;
2460 foreach my $type(keys %{$tmp_classes}) {
2461 if(defined($tmp_classes->{$type}[0]) && (!($tmp_classes->{$type}[0] =~ /^.*?removed.*?$/))) {
2462 my $entry = {
2463 type => $type,
2464 state => $tmp_classes->{$type}[0],
2465 };
2466 push @result, $entry;
2467 }
2468 }
2470 return @result;
2471 }
2473 sub get_fai_state {
2474 my $result = "";
2475 my $tmp_classes = shift || return $result;
2477 foreach my $type(keys %{$tmp_classes}) {
2478 if(defined($tmp_classes->{$type}[0])) {
2479 $result = $tmp_classes->{$type}[0];
2481 # State is equal for all types in class
2482 last;
2483 }
2484 }
2486 return $result;
2487 }
2489 sub resolve_fai_classes {
2490 my ($fai_base, $ldap_handle, $session_id) = @_;
2491 if (not defined $session_id) { $session_id = 0; }
2492 my $result;
2493 my @possible_fai_classes= ("FAIscript", "FAIhook", "FAIpartitionTable", "FAItemplate", "FAIvariable", "FAIprofile", "FAIpackageList");
2494 my $fai_filter= "(|(&(objectClass=FAIclass)(|(objectClass=".join(")(objectClass=", @possible_fai_classes).")))(objectClass=FAIbranch))";
2495 my $fai_classes;
2497 daemon_log("$session_id DEBUG: Searching for FAI entries in base $fai_base",7);
2498 my $mesg= $ldap_handle->search(
2499 base => $fai_base,
2500 scope => 'sub',
2501 attrs => ['cn','objectClass','FAIstate'],
2502 filter => $fai_filter,
2503 );
2504 daemon_log("$session_id DEBUG: Found ".$mesg->count()." FAI entries",7);
2506 if($mesg->{'resultCode'} == 0 &&
2507 $mesg->count != 0) {
2508 foreach my $entry (@{$mesg->{entries}}) {
2509 if($entry->exists('cn')) {
2510 my $tmp_dn= $entry->dn();
2512 # Skip classname and ou dn parts for class
2513 my $tmp_release = ($1) if $tmp_dn =~ /^[^,]+,[^,]+,(.*?),$fai_base$/;
2515 # Skip classes without releases
2516 if((!defined($tmp_release)) || length($tmp_release)==0) {
2517 next;
2518 }
2520 my $tmp_cn= $entry->get_value('cn');
2521 my $tmp_state= $entry->get_value('FAIstate');
2523 my $tmp_type;
2524 # Get FAI type
2525 for my $oclass(@{$entry->get_value('objectClass', asref => 1)}) {
2526 if(grep $_ eq $oclass, @possible_fai_classes) {
2527 $tmp_type= $oclass;
2528 last;
2529 }
2530 }
2532 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
2533 # A Subrelease
2534 my @sub_releases = split(/,/, $tmp_release);
2536 # Walk through subreleases and build hash tree
2537 my $hash;
2538 while(my $tmp_sub_release = pop @sub_releases) {
2539 $hash .= "\{'$tmp_sub_release'\}->";
2540 }
2541 eval('push @{$fai_classes->'.$hash.'{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";');
2542 } else {
2543 # A branch, no subrelease
2544 push @{$fai_classes->{$tmp_release}->{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";
2545 }
2546 } elsif (!$entry->exists('cn')) {
2547 my $tmp_dn= $entry->dn();
2548 my $tmp_release = ($1) if $tmp_dn =~ /^(.*?),$fai_base$/;
2550 # Skip classes without releases
2551 if((!defined($tmp_release)) || length($tmp_release)==0) {
2552 next;
2553 }
2555 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
2556 # A Subrelease
2557 my @sub_releases= split(/,/, $tmp_release);
2559 # Walk through subreleases and build hash tree
2560 my $hash;
2561 while(my $tmp_sub_release = pop @sub_releases) {
2562 $hash .= "\{'$tmp_sub_release'\}->";
2563 }
2564 # Remove the last two characters
2565 chop($hash);
2566 chop($hash);
2568 eval('$fai_classes->'.$hash.'= {}');
2569 } else {
2570 # A branch, no subrelease
2571 if(!exists($fai_classes->{$tmp_release})) {
2572 $fai_classes->{$tmp_release} = {};
2573 }
2574 }
2575 }
2576 }
2578 # The hash is complete, now we can honor the copy-on-write based missing entries
2579 foreach my $release (keys %$fai_classes) {
2580 $result->{$release}= deep_copy(apply_fai_inheritance($fai_classes->{$release}));
2581 }
2582 }
2583 return $result;
2584 }
2586 sub apply_fai_inheritance {
2587 my $fai_classes = shift || return {};
2588 my $tmp_classes;
2590 # Get the classes from the branch
2591 foreach my $class (keys %{$fai_classes}) {
2592 # Skip subreleases
2593 if($class =~ /^ou=.*$/) {
2594 next;
2595 } else {
2596 $tmp_classes->{$class}= deep_copy($fai_classes->{$class});
2597 }
2598 }
2600 # Apply to each subrelease
2601 foreach my $subrelease (keys %{$fai_classes}) {
2602 if($subrelease =~ /ou=/) {
2603 foreach my $tmp_class (keys %{$tmp_classes}) {
2604 if(!exists($fai_classes->{$subrelease}->{$tmp_class})) {
2605 $fai_classes->{$subrelease}->{$tmp_class} =
2606 deep_copy($tmp_classes->{$tmp_class});
2607 } else {
2608 foreach my $type (keys %{$tmp_classes->{$tmp_class}}) {
2609 if(!exists($fai_classes->{$subrelease}->{$tmp_class}->{$type})) {
2610 $fai_classes->{$subrelease}->{$tmp_class}->{$type}=
2611 deep_copy($tmp_classes->{$tmp_class}->{$type});
2612 }
2613 }
2614 }
2615 }
2616 }
2617 }
2619 # Find subreleases in deeper levels
2620 foreach my $subrelease (keys %{$fai_classes}) {
2621 if($subrelease =~ /ou=/) {
2622 foreach my $subsubrelease (keys %{$fai_classes->{$subrelease}}) {
2623 if($subsubrelease =~ /ou=/) {
2624 apply_fai_inheritance($fai_classes->{$subrelease});
2625 }
2626 }
2627 }
2628 }
2630 return $fai_classes;
2631 }
2633 sub get_fai_release_entries {
2634 my $tmp_classes = shift || return;
2635 my $parent = shift || "";
2636 my @result = shift || ();
2638 foreach my $entry (keys %{$tmp_classes}) {
2639 if(defined($entry)) {
2640 if($entry =~ /^ou=.*$/) {
2641 my $release_name = $entry;
2642 $release_name =~ s/ou=//g;
2643 if(length($parent)>0) {
2644 $release_name = $parent."/".$release_name;
2645 }
2646 my @bufentries = get_fai_release_entries($tmp_classes->{$entry}, $release_name, @result);
2647 foreach my $bufentry(@bufentries) {
2648 push @result, $bufentry;
2649 }
2650 } else {
2651 my @types = get_fai_types($tmp_classes->{$entry});
2652 foreach my $type (@types) {
2653 push @result,
2654 {
2655 'class' => $entry,
2656 'type' => $type->{'type'},
2657 'release' => $parent,
2658 'state' => $type->{'state'},
2659 };
2660 }
2661 }
2662 }
2663 }
2665 return @result;
2666 }
2668 sub deep_copy {
2669 my $this = shift;
2670 if (not ref $this) {
2671 $this;
2672 } elsif (ref $this eq "ARRAY") {
2673 [map deep_copy($_), @$this];
2674 } elsif (ref $this eq "HASH") {
2675 +{map { $_ => deep_copy($this->{$_}) } keys %$this};
2676 } else { die "what type is $_?" }
2677 }
2680 sub session_run_result {
2681 my ($kernel, $heap, $client_answer) = @_[KERNEL, HEAP, ARG0];
2682 $kernel->sig(CHLD => "child_reap");
2683 }
2685 sub session_run_debug {
2686 my $result = $_[ARG0];
2687 print STDERR "$result\n";
2688 }
2690 sub session_run_done {
2691 my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
2692 delete $heap->{task}->{$task_id};
2693 }
2696 sub create_sources_list {
2697 my $session_id = shift;
2698 my $ldap_handle = &main::get_ldap_handle;
2699 my $result="/tmp/gosa_si_tmp_sources_list";
2701 # Remove old file
2702 if(stat($result)) {
2703 unlink($result);
2704 &main::daemon_log("$session_id DEBUG: remove an old version of '$result'", 7);
2705 }
2707 my $fh;
2708 open($fh, ">$result");
2709 if (not defined $fh) {
2710 &main::daemon_log("$session_id DEBUG: cannot open '$result' for writing", 7);
2711 return undef;
2712 }
2713 if(defined($main::ldap_server_dn) and length($main::ldap_server_dn) > 0) {
2714 my $mesg=$ldap_handle->search(
2715 base => $main::ldap_server_dn,
2716 scope => 'base',
2717 attrs => 'FAIrepository',
2718 filter => 'objectClass=FAIrepositoryServer'
2719 );
2720 if($mesg->count) {
2721 foreach my $entry(@{$mesg->{'entries'}}) {
2722 foreach my $value(@{$entry->get_value('FAIrepository', asref => 1)}) {
2723 my ($server, $tag, $release, $sections)= split /\|/, $value;
2724 my $line = "deb $server $release";
2725 $sections =~ s/,/ /g;
2726 $line.= " $sections";
2727 print $fh $line."\n";
2728 }
2729 }
2730 }
2731 } else {
2732 if (defined $main::ldap_server_dn){
2733 &main::daemon_log("$session_id ERROR: something wrong with ldap_server_dn '$main::ldap_server_dn', abort create_sources_list", 1);
2734 } else {
2735 &main::daemon_log("$session_id ERROR: no ldap_server_dn found, abort create_sources_list", 1);
2736 }
2737 }
2738 close($fh);
2740 return $result;
2741 }
2744 sub run_create_packages_list_db {
2745 my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
2746 my $session_id = $session->ID;
2748 my $task = POE::Wheel::Run->new(
2749 Priority => +20,
2750 Program => sub {&create_packages_list_db(undef, undef, $session_id)},
2751 StdoutEvent => "session_run_result",
2752 StderrEvent => "session_run_debug",
2753 CloseEvent => "session_run_done",
2754 );
2755 $heap->{task}->{ $task->ID } = $task;
2756 }
2759 sub create_packages_list_db {
2760 my ($ldap_handle, $sources_file, $session_id) = @_;
2762 # it should not be possible to trigger a recreation of packages_list_db
2763 # while packages_list_db is under construction, so set flag packages_list_under_construction
2764 # which is tested befor recreation can be started
2765 if (-r $packages_list_under_construction) {
2766 daemon_log("$session_id WARNING: packages_list_db is right now under construction, please wait until this process is finished", 3);
2767 return;
2768 } else {
2769 daemon_log("$session_id INFO: create_packages_list_db: start", 5);
2770 # set packages_list_under_construction to true
2771 system("touch $packages_list_under_construction");
2772 @packages_list_statements=();
2773 }
2775 if (not defined $session_id) { $session_id = 0; }
2776 if (not defined $ldap_handle) {
2777 $ldap_handle= &get_ldap_handle();
2779 if (not defined $ldap_handle) {
2780 daemon_log("$session_id ERROR: no ldap_handle available to create_packages_list_db", 1);
2781 unlink($packages_list_under_construction);
2782 return;
2783 }
2784 }
2785 if (not defined $sources_file) {
2786 &main::daemon_log("$session_id INFO: no sources_file given for creating packages list so trigger creation of it", 5);
2787 $sources_file = &create_sources_list($session_id);
2788 }
2790 if (not defined $sources_file) {
2791 &main::daemon_log("$session_id ERROR: no sources_file given under '$sources_file', skip create_packages_list_db", 1);
2792 unlink($packages_list_under_construction);
2793 return;
2794 }
2796 my $line;
2798 open(CONFIG, "<$sources_file") or do {
2799 daemon_log( "$session_id ERROR: create_packages_list_db: Failed to open '$sources_file'", 1);
2800 unlink($packages_list_under_construction);
2801 return;
2802 };
2804 # Read lines
2805 while ($line = <CONFIG>){
2806 # Unify
2807 chop($line);
2808 $line =~ s/^\s+//;
2809 $line =~ s/^\s+/ /;
2811 # Strip comments
2812 $line =~ s/#.*$//g;
2814 # Skip empty lines
2815 if ($line =~ /^\s*$/){
2816 next;
2817 }
2819 # Interpret deb line
2820 if ($line =~ /^deb [^\s]+\s[^\s]+\s[^\s]+/){
2821 my( $baseurl, $dist, $sections ) = ($line =~ /^deb\s([^\s]+)\s+([^\s]+)\s+(.*)$/);
2822 my $section;
2823 foreach $section (split(' ', $sections)){
2824 &parse_package_info( $baseurl, $dist, $section, $session_id );
2825 }
2826 }
2827 }
2829 close (CONFIG);
2832 if(keys(%repo_dirs)) {
2833 find(\&cleanup_and_extract, keys( %repo_dirs ));
2834 &main::strip_packages_list_statements();
2835 unshift @packages_list_statements, "VACUUM";
2836 $packages_list_db->exec_statementlist(\@packages_list_statements);
2837 }
2838 unlink($packages_list_under_construction);
2839 daemon_log("$session_id INFO: create_packages_list_db: finished", 5);
2840 return;
2841 }
2843 # This function should do some intensive task to minimize the db-traffic
2844 sub strip_packages_list_statements {
2845 my @existing_entries= @{$packages_list_db->exec_statement("SELECT * FROM $main::packages_list_tn")};
2846 my @new_statement_list=();
2847 my $hash;
2848 my $insert_hash;
2849 my $update_hash;
2850 my $delete_hash;
2851 my $local_timestamp=get_time();
2853 foreach my $existing_entry (@existing_entries) {
2854 $hash->{@{$existing_entry}[0]}->{@{$existing_entry}[1]}->{@{$existing_entry}[2]}= $existing_entry;
2855 }
2857 foreach my $statement (@packages_list_statements) {
2858 if($statement =~ /^INSERT/i) {
2859 # Assign the values from the insert statement
2860 my ($distribution,$package,$version,$section,$description,$template,$timestamp) = ($1,$2,$3,$4,$5,$6,$7) if $statement =~
2861 /^INSERT\s+?INTO\s+?$main::packages_list_tn\s+?VALUES\s*?\('(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)'\s*?\)$/si;
2862 if(exists($hash->{$distribution}->{$package}->{$version})) {
2863 # If section or description has changed, update the DB
2864 if(
2865 (! (@{$hash->{$distribution}->{$package}->{$version}}[3] eq $section)) or
2866 (! (@{$hash->{$distribution}->{$package}->{$version}}[4] eq $description))
2867 ) {
2868 @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,undef);
2869 }
2870 } else {
2871 # Insert a non-existing entry to db
2872 @{$insert_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
2873 }
2874 } elsif ($statement =~ /^UPDATE/i) {
2875 my ($template,$package,$version) = ($1,$2,$3) if $statement =~
2876 /^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;
2877 foreach my $distribution (keys %{$hash}) {
2878 if(exists($insert_hash->{$distribution}->{$package}->{$version})) {
2879 # update the insertion hash to execute only one query per package (insert instead insert+update)
2880 @{$insert_hash->{$distribution}->{$package}->{$version}}[5]= $template;
2881 } elsif(exists($hash->{$distribution}->{$package}->{$version})) {
2882 if( ! (@{$hash->{$distribution}->{$package}->{$version}}[5] eq $template)) {
2883 my $section;
2884 my $description;
2885 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) and
2886 length(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) > 0 ) {
2887 $section= @{$update_hash->{$distribution}->{$package}->{$version}}[3];
2888 }
2889 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
2890 $description= @{$update_hash->{$distribution}->{$package}->{$version}}[4];
2891 }
2892 @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
2893 }
2894 }
2895 }
2896 }
2897 }
2899 # TODO: Check for orphaned entries
2901 # unroll the insert_hash
2902 foreach my $distribution (keys %{$insert_hash}) {
2903 foreach my $package (keys %{$insert_hash->{$distribution}}) {
2904 foreach my $version (keys %{$insert_hash->{$distribution}->{$package}}) {
2905 push @new_statement_list, "INSERT INTO $main::packages_list_tn VALUES ('$distribution','$package','$version',"
2906 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[3]',"
2907 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[4]',"
2908 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[5]',"
2909 ."'$local_timestamp')";
2910 }
2911 }
2912 }
2914 # unroll the update hash
2915 foreach my $distribution (keys %{$update_hash}) {
2916 foreach my $package (keys %{$update_hash->{$distribution}}) {
2917 foreach my $version (keys %{$update_hash->{$distribution}->{$package}}) {
2918 my $set = "";
2919 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3])) {
2920 $set .= "section = '@{$update_hash->{$distribution}->{$package}->{$version}}[3]', ";
2921 }
2922 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
2923 $set .= "description = '@{$update_hash->{$distribution}->{$package}->{$version}}[4]', ";
2924 }
2925 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[5])) {
2926 $set .= "template = '@{$update_hash->{$distribution}->{$package}->{$version}}[5]', ";
2927 }
2928 if(defined($set) and length($set) > 0) {
2929 $set .= "timestamp = '$local_timestamp'";
2930 } else {
2931 next;
2932 }
2933 push @new_statement_list,
2934 "UPDATE $main::packages_list_tn SET $set WHERE"
2935 ." distribution = '$distribution'"
2936 ." AND package = '$package'"
2937 ." AND version = '$version'";
2938 }
2939 }
2940 }
2942 @packages_list_statements = @new_statement_list;
2943 }
2946 sub parse_package_info {
2947 my ($baseurl, $dist, $section, $session_id)= @_;
2948 my ($package);
2949 if (not defined $session_id) { $session_id = 0; }
2950 my ($path) = ($baseurl =~ m%://[^/]*(.*)$%);
2951 $repo_dirs{ "${repo_path}/pool" } = 1;
2953 foreach $package ("Packages.gz"){
2954 daemon_log("$session_id DEBUG: create_packages_list: fetch $baseurl, $dist, $section", 7);
2955 get_package( "$baseurl/dists/$dist/$section/binary-$arch/$package", "$outdir/$dist/$section", $session_id );
2956 parse_package( "$outdir/$dist/$section", $dist, $path, $session_id );
2957 }
2959 }
2962 sub get_package {
2963 my ($url, $dest, $session_id)= @_;
2964 if (not defined $session_id) { $session_id = 0; }
2966 my $tpath = dirname($dest);
2967 -d "$tpath" || mkpath "$tpath";
2969 # This is ugly, but I've no time to take a look at "how it works in perl"
2970 if(0 == system("wget '$url' -O '$dest' 2>/dev/null") ) {
2971 system("gunzip -cd '$dest' > '$dest.in'");
2972 daemon_log("$session_id DEBUG: run command: gunzip -cd '$dest' > '$dest.in'", 5);
2973 unlink($dest);
2974 daemon_log("$session_id DEBUG: delete file '$dest'", 5);
2975 } else {
2976 daemon_log("$session_id ERROR: create_packages_list_db: get_packages: fetching '$url' failed!", 1);
2977 }
2978 return 0;
2979 }
2982 sub parse_package {
2983 my ($path, $dist, $srv_path, $session_id)= @_;
2984 if (not defined $session_id) { $session_id = 0;}
2985 my ($package, $version, $section, $description);
2986 my $PACKAGES;
2987 my $timestamp = &get_time();
2989 if(not stat("$path.in")) {
2990 daemon_log("$session_id ERROR: create_packages_list: parse_package: file '$path.in' is not readable",1);
2991 return;
2992 }
2994 open($PACKAGES, "<$path.in");
2995 if(not defined($PACKAGES)) {
2996 daemon_log("$session_id ERROR: create_packages_list_db: parse_package: cannot open '$path.in'",1);
2997 return;
2998 }
3000 # Read lines
3001 while (<$PACKAGES>){
3002 my $line = $_;
3003 # Unify
3004 chop($line);
3006 # Use empty lines as a trigger
3007 if ($line =~ /^\s*$/){
3008 my $sql = "INSERT INTO packages_list VALUES ('$dist', '$package', '$version', '$section', '$description', '', '$timestamp')";
3009 push(@packages_list_statements, $sql);
3010 $package = "none";
3011 $version = "none";
3012 $section = "none";
3013 $description = "none";
3014 next;
3015 }
3017 # Trigger for package name
3018 if ($line =~ /^Package:\s/){
3019 ($package)= ($line =~ /^Package: (.*)$/);
3020 next;
3021 }
3023 # Trigger for version
3024 if ($line =~ /^Version:\s/){
3025 ($version)= ($line =~ /^Version: (.*)$/);
3026 next;
3027 }
3029 # Trigger for description
3030 if ($line =~ /^Description:\s/){
3031 ($description)= &encode_base64(($line =~ /^Description: (.*)$/));
3032 next;
3033 }
3035 # Trigger for section
3036 if ($line =~ /^Section:\s/){
3037 ($section)= ($line =~ /^Section: (.*)$/);
3038 next;
3039 }
3041 # Trigger for filename
3042 if ($line =~ /^Filename:\s/){
3043 my ($filename) = ($line =~ /^Filename: (.*)$/);
3044 store_fileinfo( $package, $filename, $dist, $srv_path, $version, $repo_path );
3045 next;
3046 }
3047 }
3049 close( $PACKAGES );
3050 unlink( "$path.in" );
3051 }
3054 sub store_fileinfo {
3055 my( $package, $file, $dist, $path, $vers, $srvdir) = @_;
3057 my %fileinfo = (
3058 'package' => $package,
3059 'dist' => $dist,
3060 'version' => $vers,
3061 );
3063 $repo_files{ "${srvdir}/$file" } = \%fileinfo;
3064 }
3067 sub cleanup_and_extract {
3068 my $fileinfo = $repo_files{ $File::Find::name };
3070 if( defined $fileinfo ) {
3072 my $dir = "$outdir/$fileinfo->{ 'dist' }/debconf.d";
3073 my $sql;
3074 my $package = $fileinfo->{ 'package' };
3075 my $newver = $fileinfo->{ 'version' };
3077 mkpath($dir);
3078 system( "dpkg -e '$File::Find::name' '$dir/DEBIAN'" );
3080 if( -f "$dir/DEBIAN/templates" ) {
3082 daemon_log("DEBUG: Found debconf templates in '$package' - $newver", 7);
3084 my $tmpl= "";
3085 {
3086 local $/=undef;
3087 open FILE, "$dir/DEBIAN/templates";
3088 $tmpl = &encode_base64(<FILE>);
3089 close FILE;
3090 }
3091 rmtree("$dir/DEBIAN/templates");
3093 $sql= "update $main::packages_list_tn set template = '$tmpl' where package = '$package' and version = '$newver';";
3094 push @packages_list_statements, $sql;
3095 }
3096 }
3098 return;
3099 }
3102 sub register_at_foreign_servers {
3103 my ($kernel) = $_[KERNEL];
3105 # hole alle bekannten server aus known_server_db
3106 my $server_sql = "SELECT * FROM $known_server_tn";
3107 my $server_res = $known_server_db->exec_statement($server_sql);
3109 # no entries in known_server_db
3110 if (not ref(@$server_res[0]) eq "ARRAY") {
3111 # TODO
3112 }
3114 # detect already connected clients
3115 my $client_sql = "SELECT * FROM $known_clients_tn";
3116 my $client_res = $known_clients_db->exec_statement($client_sql);
3118 # send my server details to all other gosa-si-server within the network
3119 foreach my $hit (@$server_res) {
3120 my $hostname = @$hit[0];
3121 my $hostkey = &create_passwd;
3123 # add already connected clients to registration message
3124 my $myhash = &create_xml_hash('new_server', $server_address, $hostname);
3125 &add_content2xml_hash($myhash, 'key', $hostkey);
3126 map(&add_content2xml_hash($myhash, 'client', @{$_}[0].",".@{$_}[4]), @$client_res);
3128 # add locally loaded gosa-si modules to registration message
3129 my $loaded_modules = {};
3130 while (my ($package, $pck_info) = each %$known_modules) {
3131 next if ((!defined(@$pck_info[2])) || (!(ref (@$pck_info[2]) eq 'HASH')));
3132 foreach my $act_module (keys(%{@$pck_info[2]})) {
3133 $loaded_modules->{$act_module} = "";
3134 }
3135 }
3137 map(&add_content2xml_hash($myhash, "loaded_modules", $_), keys(%$loaded_modules));
3139 # add macaddress to registration message
3140 my ($host_ip, $host_port) = split(/:/, $hostname);
3141 my $local_ip = &get_local_ip_for_remote_ip($host_ip);
3142 my $network_interface= &get_interface_for_ip($local_ip);
3143 my $host_mac = &get_mac_for_interface($network_interface);
3144 &add_content2xml_hash($myhash, 'macaddress', $host_mac);
3146 # build registration message and send it
3147 my $foreign_server_msg = &create_xml_string($myhash);
3148 my $error = &send_msg_to_target($foreign_server_msg, $hostname, $ServerPackages_key, "new_server", 0);
3149 }
3151 $kernel->delay_set("register_at_foreign_servers", $foreign_servers_register_delay);
3152 return;
3153 }
3156 #==== MAIN = main ==============================================================
3157 # parse commandline options
3158 Getopt::Long::Configure( "bundling" );
3159 GetOptions("h|help" => \&usage,
3160 "c|config=s" => \$cfg_file,
3161 "f|foreground" => \$foreground,
3162 "v|verbose+" => \$verbose,
3163 "no-arp+" => \$no_arp,
3164 );
3166 # read and set config parameters
3167 &check_cmdline_param ;
3168 &read_configfile($cfg_file, %cfg_defaults);
3169 &check_pid;
3171 $SIG{CHLD} = 'IGNORE';
3173 # forward error messages to logfile
3174 if( ! $foreground ) {
3175 open( STDIN, '+>/dev/null' );
3176 open( STDOUT, '+>&STDIN' );
3177 open( STDERR, '+>&STDIN' );
3178 }
3180 # Just fork, if we are not in foreground mode
3181 if( ! $foreground ) {
3182 chdir '/' or die "Can't chdir to /: $!";
3183 $pid = fork;
3184 setsid or die "Can't start a new session: $!";
3185 umask 0;
3186 } else {
3187 $pid = $$;
3188 }
3190 # Do something useful - put our PID into the pid_file
3191 if( 0 != $pid ) {
3192 open( LOCK_FILE, ">$pid_file" );
3193 print LOCK_FILE "$pid\n";
3194 close( LOCK_FILE );
3195 if( !$foreground ) {
3196 exit( 0 )
3197 };
3198 }
3200 # parse head url and revision from svn
3201 my $server_status_hash = { 'developmental'=>'revision', 'stable'=>'release'};
3202 $server_version =~ /^\$HeadURL: (\S+) \$:\$Rev: (\d+) \$$/;
3203 $server_headURL = defined $1 ? $1 : 'unknown' ;
3204 $server_revision = defined $2 ? $2 : 'unknown' ;
3205 if ($server_headURL =~ /\/tag\// ||
3206 $server_headURL =~ /\/branches\// ) {
3207 $server_status = "stable";
3208 } else {
3209 $server_status = "developmental" ;
3210 }
3212 # Prepare log file
3213 $root_uid = getpwnam('root');
3214 $adm_gid = getgrnam('adm');
3215 chmod(0640, $log_file);
3216 chown($root_uid, $adm_gid, $log_file);
3217 chown($root_uid, $adm_gid, "/var/lib/gosa-si");
3219 daemon_log(" ", 1);
3220 daemon_log("$0 started!", 1);
3221 daemon_log("status: $server_status", 1);
3222 daemon_log($server_status_hash->{$server_status}.": $server_revision", 1);
3224 # connect to incoming_db
3225 unlink($incoming_file_name);
3226 $incoming_db = GOSA::DBsqlite->new($incoming_file_name);
3227 $incoming_db->create_table($incoming_tn, \@incoming_col_names);
3229 # connect to gosa-si job queue
3230 unlink($job_queue_file_name); ## just for debugging
3231 $job_db = GOSA::DBsqlite->new($job_queue_file_name);
3232 $job_db->create_table($job_queue_tn, \@job_queue_col_names);
3233 chmod(0660, $job_queue_file_name);
3234 chown($root_uid, $adm_gid, $job_queue_file_name);
3236 # connect to known_clients_db
3237 unlink($known_clients_file_name); ## just for debugging
3238 $known_clients_db = GOSA::DBsqlite->new($known_clients_file_name);
3239 $known_clients_db->create_table($known_clients_tn, \@known_clients_col_names);
3240 chmod(0660, $known_clients_file_name);
3241 chown($root_uid, $adm_gid, $known_clients_file_name);
3243 # connect to foreign_clients_db
3244 unlink($foreign_clients_file_name);
3245 $foreign_clients_db = GOSA::DBsqlite->new($foreign_clients_file_name);
3246 $foreign_clients_db->create_table($foreign_clients_tn, \@foreign_clients_col_names);
3247 chmod(0660, $foreign_clients_file_name);
3248 chown($root_uid, $adm_gid, $foreign_clients_file_name);
3250 # connect to known_server_db
3251 unlink($known_server_file_name);
3252 $known_server_db = GOSA::DBsqlite->new($known_server_file_name);
3253 $known_server_db->create_table($known_server_tn, \@known_server_col_names);
3254 chmod(0660, $known_server_file_name);
3255 chown($root_uid, $adm_gid, $known_server_file_name);
3257 # connect to login_usr_db
3258 unlink($login_users_file_name);
3259 $login_users_db = GOSA::DBsqlite->new($login_users_file_name);
3260 $login_users_db->create_table($login_users_tn, \@login_users_col_names);
3261 chmod(0660, $login_users_file_name);
3262 chown($root_uid, $adm_gid, $login_users_file_name);
3264 # connect to fai_server_db
3265 unlink($fai_server_file_name);
3266 $fai_server_db = GOSA::DBsqlite->new($fai_server_file_name);
3267 $fai_server_db->create_table($fai_server_tn, \@fai_server_col_names);
3268 chmod(0660, $fai_server_file_name);
3269 chown($root_uid, $adm_gid, $fai_server_file_name);
3271 # connect to fai_release_db
3272 unlink($fai_release_file_name);
3273 $fai_release_db = GOSA::DBsqlite->new($fai_release_file_name);
3274 $fai_release_db->create_table($fai_release_tn, \@fai_release_col_names);
3275 chmod(0660, $fai_release_file_name);
3276 chown($root_uid, $adm_gid, $fai_release_file_name);
3278 # connect to packages_list_db
3279 #unlink($packages_list_file_name);
3280 unlink($packages_list_under_construction);
3281 $packages_list_db = GOSA::DBsqlite->new($packages_list_file_name);
3282 $packages_list_db->create_table($packages_list_tn, \@packages_list_col_names);
3283 chmod(0660, $packages_list_file_name);
3284 chown($root_uid, $adm_gid, $packages_list_file_name);
3286 # connect to messaging_db
3287 unlink($messaging_file_name);
3288 $messaging_db = GOSA::DBsqlite->new($messaging_file_name);
3289 $messaging_db->create_table($messaging_tn, \@messaging_col_names);
3290 chmod(0660, $messaging_file_name);
3291 chown($root_uid, $adm_gid, $messaging_file_name);
3294 # create xml object used for en/decrypting
3295 $xml = new XML::Simple();
3298 # foreign servers
3299 my @foreign_server_list;
3301 # add foreign server from cfg file
3302 if ($foreign_server_string ne "") {
3303 my @cfg_foreign_server_list = split(",", $foreign_server_string);
3304 foreach my $foreign_server (@cfg_foreign_server_list) {
3305 push(@foreign_server_list, $foreign_server);
3306 }
3308 daemon_log("0 INFO: found foreign server in config file: ".join(", ", @foreign_server_list), 5);
3309 }
3311 # Perform a DNS lookup for server registration if flag is true
3312 if ($dns_lookup eq "true") {
3313 # Add foreign server from dns
3314 my @tmp_servers;
3315 if (not $server_domain) {
3316 # Try our DNS Searchlist
3317 for my $domain(get_dns_domains()) {
3318 chomp($domain);
3319 my ($tmp_domains, $error_string) = &get_server_addresses($domain);
3320 if(@$tmp_domains) {
3321 for my $tmp_server(@$tmp_domains) {
3322 push @tmp_servers, $tmp_server;
3323 }
3324 }
3325 }
3326 if(@tmp_servers && length(@tmp_servers)==0) {
3327 daemon_log("0 WARNING: no foreign gosa-si-server found in DNS for domain '$server_domain'", 3);
3328 }
3329 } else {
3330 @tmp_servers = &get_server_addresses($server_domain);
3331 if( 0 == @tmp_servers ) {
3332 daemon_log("0 WARNING: no foreign gosa-si-server found in DNS for domain '$server_domain'", 3);
3333 }
3334 }
3336 daemon_log("0 INFO: found foreign server via DNS ".join(", ", @tmp_servers), 5);
3338 foreach my $server (@tmp_servers) {
3339 unshift(@foreign_server_list, $server);
3340 }
3341 } else {
3342 daemon_log("0 INFO: DNS lookup for server registration is disabled", 5);
3343 }
3346 # eliminate duplicate entries
3347 @foreign_server_list = &del_doubles(@foreign_server_list);
3348 my $all_foreign_server = join(", ", @foreign_server_list);
3349 daemon_log("0 INFO: found foreign server in config file and DNS: '$all_foreign_server'", 5);
3351 # add all found foreign servers to known_server
3352 my $act_timestamp = &get_time();
3353 foreach my $foreign_server (@foreign_server_list) {
3355 # do not add myself to known_server_db
3356 if (&is_local($foreign_server)) { next; }
3357 ######################################
3359 my $res = $known_server_db->add_dbentry( {table=>$known_server_tn,
3360 primkey=>['hostname'],
3361 hostname=>$foreign_server,
3362 macaddress=>"",
3363 status=>'not_jet_registered',
3364 hostkey=>"none",
3365 loaded_modules => "none",
3366 timestamp=>$act_timestamp,
3367 } );
3368 }
3371 # Import all modules
3372 &import_modules;
3374 # Check wether all modules are gosa-si valid passwd check
3375 &password_check;
3377 # Prepare for using Opsi
3378 if ($opsi_enabled eq "true") {
3379 use JSON::RPC::Client;
3380 use XML::Quote qw(:all);
3381 $opsi_url= "https://".$opsi_admin.":".$opsi_password."@".$opsi_server.":4447/rpc";
3382 $opsi_client = new JSON::RPC::Client;
3383 }
3386 POE::Component::Server::TCP->new(
3387 Alias => "TCP_SERVER",
3388 Port => $server_port,
3389 ClientInput => sub {
3390 my ($kernel, $input) = @_[KERNEL, ARG0];
3391 push(@tasks, $input);
3392 push(@msgs_to_decrypt, $input);
3393 $kernel->yield("msg_to_decrypt");
3394 },
3395 InlineStates => {
3396 msg_to_decrypt => \&msg_to_decrypt,
3397 next_task => \&next_task,
3398 task_result => \&handle_task_result,
3399 task_done => \&handle_task_done,
3400 task_debug => \&handle_task_debug,
3401 child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!" },
3402 }
3403 );
3405 daemon_log("start socket for incoming xml messages at port '$server_port' ", 1);
3407 # create session for repeatedly checking the job queue for jobs
3408 POE::Session->create(
3409 inline_states => {
3410 _start => \&session_start,
3411 register_at_foreign_servers => \®ister_at_foreign_servers,
3412 sig_handler => \&sig_handler,
3413 next_task => \&next_task,
3414 task_result => \&handle_task_result,
3415 task_done => \&handle_task_done,
3416 task_debug => \&handle_task_debug,
3417 watch_for_next_tasks => \&watch_for_next_tasks,
3418 watch_for_new_messages => \&watch_for_new_messages,
3419 watch_for_delivery_messages => \&watch_for_delivery_messages,
3420 watch_for_done_messages => \&watch_for_done_messages,
3421 watch_for_new_jobs => \&watch_for_new_jobs,
3422 watch_for_modified_jobs => \&watch_for_modified_jobs,
3423 watch_for_done_jobs => \&watch_for_done_jobs,
3424 watch_for_opsi_jobs => \&watch_for_opsi_jobs,
3425 watch_for_old_known_clients => \&watch_for_old_known_clients,
3426 create_packages_list_db => \&run_create_packages_list_db,
3427 create_fai_server_db => \&run_create_fai_server_db,
3428 create_fai_release_db => \&run_create_fai_release_db,
3429 recreate_packages_db => \&run_recreate_packages_db,
3430 session_run_result => \&session_run_result,
3431 session_run_debug => \&session_run_debug,
3432 session_run_done => \&session_run_done,
3433 child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!" },
3434 }
3435 );
3438 POE::Kernel->run();
3439 exit;