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::GosaSupportDaemon;
52 use POE qw(Component::Server::TCP Wheel::Run Filter::Reference);
53 use Net::LDAP;
54 use Net::LDAP::Util qw(:escape);
55 use Time::HiRes qw( usleep);
57 my $db_module = "DBsqlite";
58 {
59 no strict "refs";
60 require ("GOSA/".$db_module.".pm");
61 ("GOSA/".$db_module)->import;
62 daemon_log("0 INFO: importing database module '$db_module'", 1);
63 }
65 my $modules_path = "/usr/lib/gosa-si/modules";
66 use lib "/usr/lib/gosa-si/modules";
68 # revision number of server and program name
69 my $server_version = '$HeadURL: https://oss.gonicus.de/repositories/gosa/trunk/gosa-si/gosa-si-server $:$Rev: 10826 $';
70 my $server_headURL;
71 my $server_revision;
72 my $server_status;
73 our $prg= basename($0);
75 our $global_kernel;
76 my ($foreground, $ping_timeout);
77 my ($server);
78 my ($gosa_server, $job_queue_timeout, $job_queue_loop_delay);
79 my ($messaging_db_loop_delay);
80 my ($procid, $pid);
81 my ($arp_fifo);
82 my ($xml);
83 my $sources_list;
84 my $max_clients;
85 my %repo_files=();
86 my $repo_path;
87 my %repo_dirs=();
89 # Variables declared in config file are always set to 'our'
90 our (%cfg_defaults, $log_file, $pid_file,
91 $server_ip, $server_port, $ClientPackages_key, $dns_lookup,
92 $arp_activ, $gosa_unit_tag,
93 $GosaPackages_key, $gosa_timeout,
94 $foreign_server_string, $server_domain, $ServerPackages_key, $foreign_servers_register_delay,
95 $wake_on_lan_passwd, $job_synchronization, $modified_jobs_loop_delay,
96 $arp_enabled, $arp_interface,
97 $opsi_enabled, $opsi_server, $opsi_admin, $opsi_password,
98 $new_systems_ou,
99 );
101 # additional variable which should be globaly accessable
102 our $server_address;
103 our $server_mac_address;
104 our $gosa_address;
105 our $no_arp;
106 our $verbose;
107 our $forground;
108 our $cfg_file;
109 our ($ldap_uri, $ldap_base, $ldap_admin_dn, $ldap_admin_password, $ldap_server_dn);
110 our ($mysql_username, $mysql_password, $mysql_database, $mysql_host);
111 our $known_modules;
112 our $root_uid;
113 our $adm_gid;
116 # specifies the verbosity of the daemon_log
117 $verbose = 0 ;
119 # if foreground is not null, script will be not forked to background
120 $foreground = 0 ;
122 # specifies the timeout seconds while checking the online status of a registrating client
123 $ping_timeout = 5;
125 $no_arp = 0;
126 my $packages_list_under_construction = "/tmp/packages_list_creation_in_progress";
127 my @packages_list_statements;
128 my $watch_for_new_jobs_in_progress = 0;
130 # holds all incoming decrypted messages
131 our $incoming_db;
132 our $incoming_tn = 'incoming';
133 my $incoming_file_name;
134 my @incoming_col_names = ("id INTEGER PRIMARY KEY auto_increment",
135 "timestamp VARCHAR(14) DEFAULT 'none'",
136 "headertag VARCHAR(255) DEFAULT 'none'",
137 "targettag VARCHAR(255) DEFAULT 'none'",
138 "xmlmessage TEXT",
139 "module VARCHAR(255) DEFAULT 'none'",
140 "sessionid VARCHAR(255) DEFAULT '0'",
141 );
143 # holds all gosa jobs
144 our $job_db;
145 our $job_queue_tn = 'jobs';
146 my $job_queue_file_name;
147 my @job_queue_col_names = ("id INTEGER PRIMARY KEY auto_increment",
148 "timestamp VARCHAR(14) DEFAULT 'none'",
149 "status VARCHAR(255) DEFAULT 'none'",
150 "result TEXT",
151 "progress VARCHAR(255) DEFAULT 'none'",
152 "headertag VARCHAR(255) DEFAULT 'none'",
153 "targettag VARCHAR(255) DEFAULT 'none'",
154 "xmlmessage TEXT",
155 "macaddress VARCHAR(17) DEFAULT 'none'",
156 "plainname VARCHAR(255) DEFAULT 'none'",
157 "siserver VARCHAR(255) DEFAULT 'none'",
158 "modified INTEGER DEFAULT '0'",
159 );
161 # holds all other gosa-si-server
162 our $known_server_db;
163 our $known_server_tn = "known_server";
164 my $known_server_file_name;
165 my @known_server_col_names = ("hostname VARCHAR(255)", "macaddress VARCHAR(17)", "status VARCHAR(255)", "hostkey VARCHAR(255)", "loaded_modules TEXT", "timestamp VARCHAR(14)");
167 # holds all registrated clients
168 our $known_clients_db;
169 our $known_clients_tn = "known_clients";
170 my $known_clients_file_name;
171 my @known_clients_col_names = ("hostname VARCHAR(255)", "status VARCHAR(255)", "hostkey VARCHAR(255)", "timestamp VARCHAR(14)", "macaddress VARCHAR(17)", "events TEXT", "keylifetime VARCHAR(255)");
173 # holds all registered clients at a foreign server
174 our $foreign_clients_db;
175 our $foreign_clients_tn = "foreign_clients";
176 my $foreign_clients_file_name;
177 my @foreign_clients_col_names = ("hostname VARCHAR(255)", "macaddress VARCHAR(17)", "regserver VARCHAR(255)", "timestamp VARCHAR(14)");
179 # holds all logged in user at each client
180 our $login_users_db;
181 our $login_users_tn = "login_users";
182 my $login_users_file_name;
183 my @login_users_col_names = ("client VARCHAR(255)", "user VARCHAR(255)", "timestamp VARCHAR(14)", "regserver VARCHAR(255) DEFAULT 'localhost'");
185 # holds all fai server, the debian release and tag
186 our $fai_server_db;
187 our $fai_server_tn = "fai_server";
188 my $fai_server_file_name;
189 our @fai_server_col_names = ("timestamp VARCHAR(14)", "server VARCHAR(255)", "fai_release VARCHAR(255)", "sections VARCHAR(255)", "tag VARCHAR(255)");
191 our $fai_release_db;
192 our $fai_release_tn = "fai_release";
193 my $fai_release_file_name;
194 our @fai_release_col_names = ("timestamp VARCHAR(14)", "fai_release VARCHAR(255)", "class VARCHAR(255)", "type VARCHAR(255)", "state VARCHAR(255)");
196 # holds all packages available from different repositories
197 our $packages_list_db;
198 our $packages_list_tn = "packages_list";
199 my $packages_list_file_name;
200 our @packages_list_col_names = ("distribution VARCHAR(255)", "package VARCHAR(255)", "version VARCHAR(255)", "section VARCHAR(255)", "description TEXT", "template LONGBLOB", "timestamp VARCHAR(14)");
201 my $outdir = "/tmp/packages_list_db";
202 my $arch = "i386";
204 # holds all messages which should be delivered to a user
205 our $messaging_db;
206 our $messaging_tn = "messaging";
207 our @messaging_col_names = ("id INTEGER", "subject TEXT", "message_from VARCHAR(255)", "message_to VARCHAR(255)",
208 "flag VARCHAR(255)", "direction VARCHAR(255)", "delivery_time VARCHAR(255)", "message TEXT", "timestamp VARCHAR(14)" );
209 my $messaging_file_name;
211 # path to directory to store client install log files
212 our $client_fai_log_dir = "/var/log/fai";
214 # queue which stores taskes until one of the $max_children children are ready to process the task
215 #my @tasks = qw();
216 my @msgs_to_decrypt = qw();
217 my $max_children = 2;
220 # loop delay for job queue to look for opsi jobs
221 my $job_queue_opsi_delay = 10;
222 our $opsi_client;
223 our $opsi_url;
225 # Lifetime of logged in user information. If no update information comes after n seconds,
226 # the user is expeceted to be no longer logged in or the host is no longer running. Because
227 # of this, the user is deleted from login_users_db
228 our $logged_in_user_date_of_expiry = 600;
231 %cfg_defaults = (
232 "general" => {
233 "log-file" => [\$log_file, "/var/run/".$prg.".log"],
234 "pid-file" => [\$pid_file, "/var/run/".$prg.".pid"],
235 },
236 "server" => {
237 "ip" => [\$server_ip, "0.0.0.0"],
238 "port" => [\$server_port, "20081"],
239 "known-clients" => [\$known_clients_file_name, '/var/lib/gosa-si/clients.db' ],
240 "known-servers" => [\$known_server_file_name, '/var/lib/gosa-si/servers.db'],
241 "incoming" => [\$incoming_file_name, '/var/lib/gosa-si/incoming.db'],
242 "login-users" => [\$login_users_file_name, '/var/lib/gosa-si/users.db'],
243 "fai-server" => [\$fai_server_file_name, '/var/lib/gosa-si/fai_server.db'],
244 "fai-release" => [\$fai_release_file_name, '/var/lib/gosa-si/fai_release.db'],
245 "packages-list" => [\$packages_list_file_name, '/var/lib/gosa-si/packages.db'],
246 "messaging" => [\$messaging_file_name, '/var/lib/gosa-si/messaging.db'],
247 "foreign-clients" => [\$foreign_clients_file_name, '/var/lib/gosa-si/foreign_clients.db'],
248 "source-list" => [\$sources_list, '/etc/apt/sources.list'],
249 "repo-path" => [\$repo_path, '/srv/www/repository'],
250 "ldap-uri" => [\$ldap_uri, ""],
251 "ldap-base" => [\$ldap_base, ""],
252 "ldap-admin-dn" => [\$ldap_admin_dn, ""],
253 "ldap-admin-password" => [\$ldap_admin_password, ""],
254 "gosa-unit-tag" => [\$gosa_unit_tag, ""],
255 "max-clients" => [\$max_clients, 10],
256 "wol-password" => [\$wake_on_lan_passwd, ""],
257 "mysql-username" => [\$mysql_username, "gosa_si"],
258 "mysql-password" => [\$mysql_password, ""],
259 "mysql-database" => [\$mysql_database, "gosa_si"],
260 "mysql-host" => [\$mysql_host, "127.0.0.1"],
261 },
262 "GOsaPackages" => {
263 "job-queue" => [\$job_queue_file_name, '/var/lib/gosa-si/jobs.db'],
264 "job-queue-loop-delay" => [\$job_queue_loop_delay, 3],
265 "messaging-db-loop-delay" => [\$messaging_db_loop_delay, 3],
266 "key" => [\$GosaPackages_key, "none"],
267 "new-systems-ou" => [\$new_systems_ou, 'ou=workstations,ou=systems'],
268 },
269 "ClientPackages" => {
270 "key" => [\$ClientPackages_key, "none"],
271 "user-date-of-expiry" => [\$logged_in_user_date_of_expiry, 600],
272 },
273 "ServerPackages"=> {
274 "address" => [\$foreign_server_string, ""],
275 "dns-lookup" => [\$dns_lookup, "true"],
276 "domain" => [\$server_domain, ""],
277 "key" => [\$ServerPackages_key, "none"],
278 "key-lifetime" => [\$foreign_servers_register_delay, 120],
279 "job-synchronization-enabled" => [\$job_synchronization, "true"],
280 "synchronization-loop" => [\$modified_jobs_loop_delay, 5],
281 },
282 "ArpHandler" => {
283 "enabled" => [\$arp_enabled, "true"],
284 "interface" => [\$arp_interface, "all"],
285 },
286 "Opsi" => {
287 "enabled" => [\$opsi_enabled, "false"],
288 "server" => [\$opsi_server, "localhost"],
289 "admin" => [\$opsi_admin, "opsi-admin"],
290 "password" => [\$opsi_password, "secret"],
291 },
293 );
296 #=== FUNCTION ================================================================
297 # NAME: usage
298 # PARAMETERS: nothing
299 # RETURNS: nothing
300 # DESCRIPTION: print out usage text to STDERR
301 #===============================================================================
302 sub usage {
303 print STDERR << "EOF" ;
304 usage: $prg [-hvf] [-c config]
306 -h : this (help) message
307 -c <file> : config file
308 -f : foreground, process will not be forked to background
309 -v : be verbose (multiple to increase verbosity)
310 -no-arp : starts $prg without connection to arp module
312 EOF
313 print "\n" ;
314 }
317 #=== FUNCTION ================================================================
318 # NAME: logging
319 # PARAMETERS: level - string - default 'info'
320 # msg - string -
321 # facility - string - default 'LOG_DAEMON'
322 # RETURNS: nothing
323 # DESCRIPTION: function for logging
324 #===============================================================================
325 sub daemon_log {
326 # log into log_file
327 my( $msg, $level ) = @_;
328 if(not defined $msg) { return }
329 if(not defined $level) { $level = 1 }
330 if(defined $log_file){
331 open(LOG_HANDLE, ">>$log_file");
332 if(not defined open( LOG_HANDLE, ">>$log_file" )) {
333 print STDERR "cannot open $log_file: $!";
334 return
335 }
336 chomp($msg);
337 #$msg =~s/\n//g; # no newlines are allowed in log messages, this is important for later log parsing
338 if($level <= $verbose){
339 my ($seconds, $minutes, $hours, $monthday, $month,
340 $year, $weekday, $yearday, $sommertime) = localtime(time);
341 $hours = $hours < 10 ? $hours = "0".$hours : $hours;
342 $minutes = $minutes < 10 ? $minutes = "0".$minutes : $minutes;
343 $seconds = $seconds < 10 ? $seconds = "0".$seconds : $seconds;
344 my @monthnames = ("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec");
345 $month = $monthnames[$month];
346 $monthday = $monthday < 10 ? $monthday = "0".$monthday : $monthday;
347 $year+=1900;
348 my $name = $prg;
350 my $log_msg = "$month $monthday $hours:$minutes:$seconds $name $msg\n";
351 print LOG_HANDLE $log_msg;
352 if( $foreground ) {
353 print STDERR $log_msg;
354 }
355 }
356 close( LOG_HANDLE );
357 }
358 }
361 #=== FUNCTION ================================================================
362 # NAME: check_cmdline_param
363 # PARAMETERS: nothing
364 # RETURNS: nothing
365 # DESCRIPTION: validates commandline parameter
366 #===============================================================================
367 sub check_cmdline_param () {
368 my $err_config;
369 my $err_counter = 0;
370 if(not defined($cfg_file)) {
371 $cfg_file = "/etc/gosa-si/server.conf";
372 if(! -r $cfg_file) {
373 $err_config = "please specify a config file";
374 $err_counter += 1;
375 }
376 }
377 if( $err_counter > 0 ) {
378 &usage( "", 1 );
379 if( defined( $err_config)) { print STDERR "$err_config\n"}
380 print STDERR "\n";
381 exit( -1 );
382 }
383 }
386 #=== FUNCTION ================================================================
387 # NAME: check_pid
388 # PARAMETERS: nothing
389 # RETURNS: nothing
390 # DESCRIPTION: handels pid processing
391 #===============================================================================
392 sub check_pid {
393 $pid = -1;
394 # Check, if we are already running
395 if( open(LOCK_FILE, "<$pid_file") ) {
396 $pid = <LOCK_FILE>;
397 if( defined $pid ) {
398 chomp( $pid );
399 if( -f "/proc/$pid/stat" ) {
400 my($stat) = `cat /proc/$pid/stat` =~ m/$pid \((.+)\).*/;
401 if( $stat ) {
402 daemon_log("ERROR: Already running",1);
403 close( LOCK_FILE );
404 exit -1;
405 }
406 }
407 }
408 close( LOCK_FILE );
409 unlink( $pid_file );
410 }
412 # create a syslog msg if it is not to possible to open PID file
413 if (not sysopen(LOCK_FILE, $pid_file, O_WRONLY|O_CREAT|O_EXCL, 0644)) {
414 my($msg) = "Couldn't obtain lockfile '$pid_file' ";
415 if (open(LOCK_FILE, '<', $pid_file)
416 && ($pid = <LOCK_FILE>))
417 {
418 chomp($pid);
419 $msg .= "(PID $pid)\n";
420 } else {
421 $msg .= "(unable to read PID)\n";
422 }
423 if( ! ($foreground) ) {
424 openlog( $0, "cons,pid", "daemon" );
425 syslog( "warning", $msg );
426 closelog();
427 }
428 else {
429 print( STDERR " $msg " );
430 }
431 exit( -1 );
432 }
433 }
435 #=== FUNCTION ================================================================
436 # NAME: import_modules
437 # PARAMETERS: module_path - string - abs. path to the directory the modules
438 # are stored
439 # RETURNS: nothing
440 # DESCRIPTION: each file in module_path which ends with '.pm' and activation
441 # state is on is imported by "require 'file';"
442 #===============================================================================
443 sub import_modules {
444 daemon_log(" ", 1);
446 if (not -e $modules_path) {
447 daemon_log("0 ERROR: cannot find directory or directory is not readable: $modules_path", 1);
448 }
450 opendir (DIR, $modules_path) or die "ERROR while loading modules from directory $modules_path : $!\n";
451 while (defined (my $file = readdir (DIR))) {
452 if (not $file =~ /(\S*?).pm$/) {
453 next;
454 }
455 my $mod_name = $1;
457 # ArpHandler switch
458 if( $file =~ /ArpHandler.pm/ ) {
459 if( $arp_enabled eq "false" ) { next; }
460 }
462 eval { require $file; };
463 if ($@) {
464 daemon_log("0 ERROR: gosa-si-server could not load module $file", 1);
465 daemon_log("$@", 1);
466 exit;
467 } else {
468 my $info = eval($mod_name.'::get_module_info()');
469 # Only load module if get_module_info() returns a non-null object
470 if( $info ) {
471 my ($input_address, $input_key, $event_hash) = @{$info};
472 $known_modules->{$mod_name} = $info;
473 daemon_log("0 INFO: module $mod_name loaded", 5);
474 }
475 }
476 }
478 close (DIR);
479 }
481 #=== FUNCTION ================================================================
482 # NAME: password_check
483 # PARAMETERS: nothing
484 # RETURNS: nothing
485 # DESCRIPTION: escalates an critical error if two modules exist which are avaialable by
486 # the same password
487 #===============================================================================
488 sub password_check {
489 my $passwd_hash = {};
490 while (my ($mod_name, $mod_info) = each %$known_modules) {
491 my $mod_passwd = @$mod_info[1];
492 if (not defined $mod_passwd) { next; }
493 if (not exists $passwd_hash->{$mod_passwd}) {
494 $passwd_hash->{$mod_passwd} = $mod_name;
496 # escalates critical error
497 } else {
498 &daemon_log("0 ERROR: two loaded modules do have the same password. Please modify the 'key'-parameter in config file");
499 &daemon_log("0 ERROR: module='$mod_name' and module='".$passwd_hash->{$mod_passwd}."'");
500 exit( -1 );
501 }
502 }
504 }
507 #=== FUNCTION ================================================================
508 # NAME: sig_int_handler
509 # PARAMETERS: signal - string - signal arose from system
510 # RETURNS: nothing
511 # DESCRIPTION: handels tasks to be done befor signal becomes active
512 #===============================================================================
513 sub sig_int_handler {
514 my ($signal) = @_;
516 # if (defined($ldap_handle)) {
517 # $ldap_handle->disconnect;
518 # }
519 # TODO alle verbliebenden ldap verbindungen aus allen heaps beenden
522 daemon_log("shutting down gosa-si-server", 1);
523 system("kill `ps -C gosa-si-server -o pid=`");
524 }
525 $SIG{INT} = \&sig_int_handler;
528 sub check_key_and_xml_validity {
529 my ($crypted_msg, $module_key, $session_id) = @_;
530 my $msg;
531 my $msg_hash;
532 my $error_string;
533 eval{
534 $msg = &decrypt_msg($crypted_msg, $module_key);
536 if ($msg =~ /<xml>/i){
537 $msg =~ s/\s+/ /g; # just for better daemon_log
538 daemon_log("$session_id DEBUG: decrypted_msg: \n$msg", 9);
539 $msg_hash = $xml->XMLin($msg, ForceArray=>1);
541 ##############
542 # check header
543 if( not exists $msg_hash->{'header'} ) { die "no header specified"; }
544 my $header_l = $msg_hash->{'header'};
545 if( (1 > @{$header_l}) || ( ( 'HASH' eq ref @{$header_l}[0]) && (1 > keys %{@{$header_l}[0]}) ) ) { die 'empty header tag'; }
546 if( 1 < @{$header_l} ) { die 'more than one header specified'; }
547 my $header = @{$header_l}[0];
548 if( 0 == length $header) { die 'empty string in header tag'; }
550 ##############
551 # check source
552 if( not exists $msg_hash->{'source'} ) { die "no source specified"; }
553 my $source_l = $msg_hash->{'source'};
554 if( (1 > @{$source_l}) || ( ( 'HASH' eq ref @{$source_l}[0]) && (1 > keys %{@{$source_l}[0]}) ) ) { die 'empty source tag'; }
555 if( 1 < @{$source_l} ) { die 'more than one source specified'; }
556 my $source = @{$source_l}[0];
557 if( 0 == length $source) { die 'source error'; }
559 ##############
560 # check target
561 if( not exists $msg_hash->{'target'} ) { die "no target specified"; }
562 my $target_l = $msg_hash->{'target'};
563 if( (1 > @{$target_l}) || ( ('HASH' eq ref @{$target_l}[0]) && (1 > keys %{@{$target_l}[0]}) ) ) { die 'empty target tag'; }
564 }
565 };
566 if($@) {
567 daemon_log("$session_id ERROR: do not understand the message: $@", 1);
568 $msg = undef;
569 $msg_hash = undef;
570 }
572 return ($msg, $msg_hash);
573 }
576 sub check_outgoing_xml_validity {
577 my ($msg, $session_id) = @_;
579 my $msg_hash;
580 eval{
581 $msg_hash = $xml->XMLin($msg, ForceArray=>1);
583 ##############
584 # check header
585 my $header_l = $msg_hash->{'header'};
586 if( 1 != @{$header_l} ) {
587 die 'no or more than one headers specified';
588 }
589 my $header = @{$header_l}[0];
590 if( 0 == length $header) {
591 die 'header has length 0';
592 }
594 ##############
595 # check source
596 my $source_l = $msg_hash->{'source'};
597 if( 1 != @{$source_l} ) {
598 die 'no or more than 1 sources specified';
599 }
600 my $source = @{$source_l}[0];
601 if( 0 == length $source) {
602 die 'source has length 0';
603 }
605 # Check if source contains hostname instead of ip address
606 if(not $source =~ /^[a-z0-9\.]+:\d+$/i) {
607 my ($hostname,$port) = split(/:/, $source);
608 my $ip_address = inet_ntoa(scalar gethostbyname($hostname));
609 if(defined($ip_address) && $ip_address =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/ && $port =~ /^\d+$/) {
610 # Write ip address to $source variable
611 $source = "$ip_address:$port";
612 }
613 }
614 unless( $source =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
615 $source =~ /^GOSA$/i) {
616 die "source '$source' is neither a complete ip-address with port nor 'GOSA'";
617 }
619 ##############
620 # check target
621 my $target_l = $msg_hash->{'target'};
622 if( 0 == @{$target_l} ) {
623 die "no targets specified";
624 }
625 foreach my $target (@$target_l) {
626 if( 0 == length $target) {
627 die "target has length 0";
628 }
629 unless( $target =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/ ||
630 $target =~ /^GOSA$/i ||
631 $target =~ /^\*$/ ||
632 $target =~ /KNOWN_SERVER/i ||
633 $target =~ /JOBDB/i ||
634 $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 ){
635 die "target '$target' is not a complete ip-address with port or a valid target name or a mac-address";
636 }
637 }
638 };
639 if($@) {
640 daemon_log("$session_id ERROR: outgoing msg is not gosa-si envelope conform: $@", 1);
641 daemon_log("$@ ".(defined($msg) && length($msg)>0)?$msg:"Empty Message", 1);
642 $msg_hash = undef;
643 }
645 return ($msg_hash);
646 }
649 sub input_from_known_server {
650 my ($input, $remote_ip, $session_id) = @_ ;
651 my ($msg, $msg_hash, $module);
653 my $sql_statement= "SELECT * FROM known_server";
654 my $query_res = $known_server_db->select_dbentry( $sql_statement );
656 while( my ($hit_num, $hit) = each %{ $query_res } ) {
657 my $host_name = $hit->{hostname};
658 if( not $host_name =~ "^$remote_ip") {
659 next;
660 }
661 my $host_key = $hit->{hostkey};
662 daemon_log("$session_id DEBUG: input_from_known_server: host_name: $host_name", 7);
663 daemon_log("$session_id DEBUG: input_from_known_server: host_key: $host_key", 7);
665 # check if module can open msg envelope with module key
666 my ($tmp_msg, $tmp_msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
667 if( (!$tmp_msg) || (!$tmp_msg_hash) ) {
668 daemon_log("$session_id DEBUG: input_from_known_server: deciphering raise error", 7);
669 daemon_log("$@", 8);
670 next;
671 }
672 else {
673 $msg = $tmp_msg;
674 $msg_hash = $tmp_msg_hash;
675 $module = "ServerPackages";
676 daemon_log("$session_id DEBUG: check_key_and_xml_validity... ok", 7);
677 last;
678 }
679 }
681 if( (!$msg) || (!$msg_hash) || (!$module) ) {
682 daemon_log("$session_id DEBUG: Incoming message is not from a known server", 7);
683 }
685 return ($msg, $msg_hash, $module);
686 }
689 sub input_from_known_client {
690 my ($input, $remote_ip, $session_id) = @_ ;
691 my ($msg, $msg_hash, $module);
693 my $sql_statement= "SELECT * FROM known_clients";
694 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
695 while( my ($hit_num, $hit) = each %{ $query_res } ) {
696 my $host_name = $hit->{hostname};
697 if( not $host_name =~ /^$remote_ip:\d*$/) {
698 next;
699 }
700 my $host_key = $hit->{hostkey};
701 &daemon_log("$session_id DEBUG: input_from_known_client: host_name: $host_name", 7);
702 &daemon_log("$session_id DEBUG: input_from_known_client: host_key: $host_key", 7);
704 # check if module can open msg envelope with module key
705 ($msg, $msg_hash) = &check_key_and_xml_validity($input, $host_key, $session_id);
707 if( (!$msg) || (!$msg_hash) ) {
708 &daemon_log("$session_id DEGUG: input_from_known_client: deciphering raise error", 7);
709 &daemon_log("$@", 8);
710 next;
711 }
712 else {
713 $module = "ClientPackages";
714 daemon_log("$session_id DEBUG: check_key_and_xml_validity... ok", 7);
715 last;
716 }
717 }
719 if( (!$msg) || (!$msg_hash) || (!$module) ) {
720 &daemon_log("$session_id DEBUG: Incoming message is not from a known client", 7);
721 }
723 return ($msg, $msg_hash, $module);
724 }
727 sub input_from_unknown_host {
728 no strict "refs";
729 my ($input, $session_id) = @_ ;
730 my ($msg, $msg_hash, $module);
731 my $error_string;
733 my %act_modules = %$known_modules;
735 while( my ($mod, $info) = each(%act_modules)) {
737 # check a key exists for this module
738 my $module_key = ${$mod."_key"};
739 if( not defined $module_key ) {
740 if( $mod eq 'ArpHandler' ) {
741 next;
742 }
743 daemon_log("$session_id ERROR: no key specified in config file for $mod", 1);
744 next;
745 }
746 daemon_log("$session_id DEBUG: $mod: $module_key", 7);
748 # check if module can open msg envelope with module key
749 ($msg, $msg_hash) = &check_key_and_xml_validity($input, $module_key, $session_id);
750 if( (not defined $msg) || (not defined $msg_hash) ) {
751 next;
752 } else {
753 $module = $mod;
754 daemon_log("$session_id DEBUG: check_key_and_xml_validity... ok", 7);
755 last;
756 }
757 }
759 if( (!$msg) || (!$msg_hash) || (!$module)) {
760 daemon_log("$session_id DEBUG: Incoming message is not from an unknown host", 7);
761 }
763 return ($msg, $msg_hash, $module);
764 }
767 sub create_ciphering {
768 my ($passwd) = @_;
769 if((!defined($passwd)) || length($passwd)==0) {
770 $passwd = "";
771 }
772 $passwd = substr(md5_hex("$passwd") x 32, 0, 32);
773 my $iv = substr(md5_hex('GONICUS GmbH'),0, 16);
774 my $my_cipher = Crypt::Rijndael->new($passwd , Crypt::Rijndael::MODE_CBC());
775 $my_cipher->set_iv($iv);
776 return $my_cipher;
777 }
780 sub encrypt_msg {
781 my ($msg, $key) = @_;
782 my $my_cipher = &create_ciphering($key);
783 my $len;
784 {
785 use bytes;
786 $len= 16-length($msg)%16;
787 }
788 $msg = "\0"x($len).$msg;
789 $msg = $my_cipher->encrypt($msg);
790 chomp($msg = &encode_base64($msg));
791 # there are no newlines allowed inside msg
792 $msg=~ s/\n//g;
793 return $msg;
794 }
797 sub decrypt_msg {
799 my ($msg, $key) = @_ ;
800 $msg = &decode_base64($msg);
801 my $my_cipher = &create_ciphering($key);
802 $msg = $my_cipher->decrypt($msg);
803 $msg =~ s/\0*//g;
804 return $msg;
805 }
808 sub get_encrypt_key {
809 my ($target) = @_ ;
810 my $encrypt_key;
811 my $error = 0;
813 # target can be in known_server
814 if( not defined $encrypt_key ) {
815 my $sql_statement= "SELECT * FROM known_server WHERE hostname='$target'";
816 my $query_res = $known_server_db->select_dbentry( $sql_statement );
817 while( my ($hit_num, $hit) = each %{ $query_res } ) {
818 my $host_name = $hit->{hostname};
819 if( $host_name ne $target ) {
820 next;
821 }
822 $encrypt_key = $hit->{hostkey};
823 last;
824 }
825 }
827 # target can be in known_client
828 if( not defined $encrypt_key ) {
829 my $sql_statement= "SELECT * FROM known_clients WHERE hostname='$target'";
830 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
831 while( my ($hit_num, $hit) = each %{ $query_res } ) {
832 my $host_name = $hit->{hostname};
833 if( $host_name ne $target ) {
834 next;
835 }
836 $encrypt_key = $hit->{hostkey};
837 last;
838 }
839 }
841 return $encrypt_key;
842 }
845 #=== FUNCTION ================================================================
846 # NAME: open_socket
847 # PARAMETERS: PeerAddr string something like 192.168.1.1 or 192.168.1.1:10000
848 # [PeerPort] string necessary if port not appended by PeerAddr
849 # RETURNS: socket IO::Socket::INET
850 # DESCRIPTION: open a socket to PeerAddr
851 #===============================================================================
852 sub open_socket {
853 my ($PeerAddr, $PeerPort) = @_ ;
854 if(defined($PeerPort)){
855 $PeerAddr = $PeerAddr.":".$PeerPort;
856 }
857 my $socket;
858 $socket = new IO::Socket::INET(PeerAddr => $PeerAddr,
859 Porto => "tcp",
860 Type => SOCK_STREAM,
861 Timeout => 5,
862 );
863 if(not defined $socket) {
864 return;
865 }
866 # &daemon_log("DEBUG: open_socket: $PeerAddr", 7);
867 return $socket;
868 }
871 #sub get_local_ip_for_remote_ip {
872 # my $remote_ip= shift;
873 # my $result="0.0.0.0";
874 #
875 # if($remote_ip =~ /^(\d\d?\d?\.){3}\d\d?\d?$/) {
876 # if($remote_ip eq "127.0.0.1") {
877 # $result = "127.0.0.1";
878 # } else {
879 # my $PROC_NET_ROUTE= ('/proc/net/route');
880 #
881 # open(PROC_NET_ROUTE, "<$PROC_NET_ROUTE")
882 # or die "Could not open $PROC_NET_ROUTE";
883 #
884 # my @ifs = <PROC_NET_ROUTE>;
885 #
886 # close(PROC_NET_ROUTE);
887 #
888 # # Eat header line
889 # shift @ifs;
890 # chomp @ifs;
891 # foreach my $line(@ifs) {
892 # my ($Iface,$Destination,$Gateway,$Flags,$RefCnt,$Use,$Metric,$Mask,$MTU,$Window,$IRTT)=split(/\s/, $line);
893 # my $destination;
894 # my $mask;
895 # my ($d,$c,$b,$a)=unpack('a2 a2 a2 a2', $Destination);
896 # $destination= sprintf("%d.%d.%d.%d", hex($a), hex($b), hex($c), hex($d));
897 # ($d,$c,$b,$a)=unpack('a2 a2 a2 a2', $Mask);
898 # $mask= sprintf("%d.%d.%d.%d", hex($a), hex($b), hex($c), hex($d));
899 # if(new NetAddr::IP($remote_ip)->within(new NetAddr::IP($destination, $mask))) {
900 # # destination matches route, save mac and exit
901 # $result= &get_ip($Iface);
902 # last;
903 # }
904 # }
905 # }
906 # } else {
907 # daemon_log("0 WARNING: get_local_ip_for_remote_ip() was called with a non-ip parameter: '$remote_ip'", 1);
908 # }
909 # return $result;
910 #}
913 sub send_msg_to_target {
914 my ($msg, $address, $encrypt_key, $msg_header, $session_id) = @_ ;
915 my $error = 0;
916 my $header;
917 my $timestamp = &get_time();
918 my $new_status;
919 my $act_status;
920 my ($sql_statement, $res);
922 if( $msg_header ) {
923 $header = "'$msg_header'-";
924 } else {
925 $header = "";
926 }
928 # Patch the source ip
929 if($msg =~ /<source>0\.0\.0\.0:\d*?<\/source>/) {
930 my $remote_ip = &get_local_ip_for_remote_ip(sprintf("%s", $address =~ /^([0-9\.]*?):.*$/));
931 $msg =~ s/<source>(0\.0\.0\.0):(\d*?)<\/source>/<source>$remote_ip:$2<\/source>/s;
932 }
934 # encrypt xml msg
935 my $crypted_msg = &encrypt_msg($msg, $encrypt_key);
937 # opensocket
938 my $socket = &open_socket($address);
939 if( !$socket ) {
940 daemon_log("$session_id WARNING: cannot send ".$header."msg to $address , host not reachable", 3);
941 $error++;
942 }
944 if( $error == 0 ) {
945 # send xml msg
946 print $socket $crypted_msg."\n";
948 daemon_log("$session_id INFO: send ".$header."msg to $address", 5);
949 daemon_log("$session_id DEBUG: message:\n$msg", 9);
951 }
953 # close socket in any case
954 if( $socket ) {
955 close $socket;
956 }
958 if( $error > 0 ) { $new_status = "down"; }
959 else { $new_status = $msg_header; }
962 # known_clients
963 $sql_statement = "SELECT * FROM $known_clients_tn WHERE hostname='$address'";
964 $res = $known_clients_db->select_dbentry($sql_statement);
965 if( keys(%$res) == 1) {
966 $act_status = exists $res->{1}->{'status'} ? $res->{1}->{'status'} : "";
967 if ($act_status eq "down" && $new_status eq "down") {
968 $sql_statement = "DELETE FROM known_clients WHERE hostname='$address'";
969 $res = $known_clients_db->del_dbentry($sql_statement);
970 daemon_log("$session_id WARNING: failed 2x to send msg to host '$address', delete host from known_clients", 3);
971 } else {
972 $sql_statement = "UPDATE known_clients SET status='$new_status', timestamp='$timestamp' WHERE hostname='$address'";
973 $res = $known_clients_db->update_dbentry($sql_statement);
974 if($new_status eq "down"){
975 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
976 } else {
977 daemon_log("$session_id INFO: set '$address' from status '$act_status' to '$new_status'", 5);
978 }
979 }
980 }
982 # known_server
983 $sql_statement = "SELECT * FROM $known_server_tn WHERE hostname='$address'";
984 $res = $known_server_db->select_dbentry($sql_statement);
985 if( keys(%$res) == 1) {
986 $act_status = exists $res->{1}->{'status'} ? $res->{1}->{'status'} : "";
987 if ($act_status eq "down" && $new_status eq "down") {
988 $sql_statement = "DELETE FROM known_server WHERE hostname='$address'";
989 $res = $known_server_db->del_dbentry($sql_statement);
990 daemon_log("$session_id WARNING: failed 2x to send a message to host '$address', delete host from known_server", 3);
991 }
992 else {
993 $sql_statement = "UPDATE known_server SET status='$new_status', timestamp='$timestamp' WHERE hostname='$address'";
994 $res = $known_server_db->update_dbentry($sql_statement);
995 if($new_status eq "down"){
996 daemon_log("$session_id WARNING: set '$address' from status '$act_status' to '$new_status'", 3);
997 } else {
998 daemon_log("$session_id INFO: set '$address' from status '$act_status' to '$new_status'", 5);
999 }
1000 }
1001 }
1002 return $error;
1003 }
1006 sub update_jobdb_status_for_send_msgs {
1007 my ($session_id, $answer, $error) = @_;
1008 if( $answer =~ /<jobdb_id>(\d+)<\/jobdb_id>/ ) {
1009 my $jobdb_id = $1;
1011 $answer =~ /<header>(.*)<\/header>/;
1012 my $job_header = $1;
1014 $answer =~ /<target>(.*)<\/target>/;
1015 my $job_target = $1;
1017 # sending msg failed
1018 if( $error ) {
1020 # set jobs to done, jobs do not need to deliver their message in any case
1021 if (($job_header eq "trigger_action_localboot")
1022 ||($job_header eq "trigger_action_lock")
1023 ||($job_header eq "trigger_action_halt")
1024 ) {
1025 my $sql_statement = "UPDATE $job_queue_tn SET status='done' WHERE id=$jobdb_id";
1026 my $res = $job_db->update_dbentry($sql_statement);
1028 # reactivate jobs, jobs need to deliver their message
1029 } elsif (($job_header eq "trigger_action_activate")
1030 ||($job_header eq "trigger_action_update")
1031 ||($job_header eq "trigger_action_reinstall")
1032 ) {
1033 &reactivate_job_with_delay($session_id, $job_target, $job_header, 30 );
1035 # for all other messages
1036 } else {
1037 my $sql_statement = "UPDATE $job_queue_tn ".
1038 "SET status='error', result='can not deliver msg, please consult log file' ".
1039 "WHERE id=$jobdb_id";
1040 my $res = $job_db->update_dbentry($sql_statement);
1041 &daemon_log("D \n$sql_statement\n$answer", 1);
1043 }
1045 # sending msg was successful
1046 } else {
1047 # set jobs localboot, lock, activate, halt, reboot and wake to done
1048 # jobs reinstall, update, inst_update do themself setting to done
1049 if (($job_header eq "trigger_action_localboot")
1050 ||($job_header eq "trigger_action_lock")
1051 ||($job_header eq "trigger_action_activate")
1052 ||($job_header eq "trigger_action_halt")
1053 ||($job_header eq "trigger_action_reboot")
1054 ||($job_header eq "trigger_action_wake")
1055 ) {
1057 my $sql_statement = "UPDATE $job_queue_tn ".
1058 "SET status='done' ".
1059 "WHERE id=$jobdb_id AND status='processed'";
1060 my $res = $job_db->update_dbentry($sql_statement);
1061 }
1062 }
1063 }
1064 }
1066 sub reactivate_job_with_delay {
1067 my ($session_id, $target, $header, $delay) = @_ ;
1068 # 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
1070 if (not defined $delay) { $delay = 30 } ;
1071 my $delay_timestamp = &calc_timestamp(&get_time(), "plus", $delay);
1073 my $sql = "UPDATE $job_queue_tn Set timestamp='$delay_timestamp', status='waiting' WHERE (macaddress='$target' AND headertag='$header')";
1074 my $res = $job_db->update_dbentry($sql);
1075 daemon_log("$session_id INFO: '$header'-job will be reactivated at '$delay_timestamp' ".
1076 "cause client '$target' is currently not available", 5);
1077 daemon_log("$session_id $sql", 7);
1078 return;
1079 }
1082 sub sig_handler {
1083 my ($kernel, $signal) = @_[KERNEL, ARG0] ;
1084 daemon_log("0 INFO got signal '$signal'", 1);
1085 $kernel->sig_handled();
1086 return;
1087 }
1090 sub msg_to_decrypt {
1091 my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
1092 my $session_id = $session->ID;
1093 my ($msg, $msg_hash, $module);
1094 my $error = 0;
1096 # hole neue msg aus @msgs_to_decrypt
1097 my $next_msg = shift @msgs_to_decrypt;
1099 # msg is from a new client or gosa
1100 ($msg, $msg_hash, $module) = &input_from_unknown_host($next_msg, $session_id);
1102 # msg is from a gosa-si-server
1103 if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1104 ($msg, $msg_hash, $module) = &input_from_known_server($next_msg, $heap->{'remote_ip'}, $session_id);
1105 }
1106 # msg is from a gosa-si-client
1107 if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1108 ($msg, $msg_hash, $module) = &input_from_known_client($next_msg, $heap->{'remote_ip'}, $session_id);
1109 }
1110 # an error occurred
1111 if(( !$msg ) || ( !$msg_hash ) || ( !$module )){
1112 # if an incoming msg could not be decrypted (maybe a wrong key), send client a ping. If the client
1113 # could not understand a msg from its server the client cause a re-registering process
1114 daemon_log("$session_id WARNING cannot understand incoming msg, send 'ping'-msg to all host with ip '".$heap->{remote_ip}.
1115 "' to cause a re-registering of the client if necessary", 3);
1116 my $sql_statement = "SELECT * FROM $main::known_clients_tn WHERE (hostname LIKE '".$heap->{'remote_ip'}."%')";
1117 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1118 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1119 my $host_name = $hit->{'hostname'};
1120 my $host_key = $hit->{'hostkey'};
1121 my $ping_msg = "<xml> <header>gosa_ping</header> <source>$server_address</source> <target>$host_name</target></xml>";
1122 my $error = &send_msg_to_target($ping_msg, $host_name, $host_key, "gosa_ping", $session_id);
1123 &update_jobdb_status_for_send_msgs($ping_msg, $error);
1124 }
1125 $error++;
1126 }
1129 my $header;
1130 my $target;
1131 my $source;
1132 my $done = 0;
1133 my $sql;
1134 my $res;
1136 # check whether this message should be processed here
1137 if ($error == 0) {
1138 $header = @{$msg_hash->{'header'}}[0];
1139 $target = @{$msg_hash->{'target'}}[0];
1140 $source = @{$msg_hash->{'source'}}[0];
1141 my $not_found_in_known_clients_db = 0;
1142 my $not_found_in_known_server_db = 0;
1143 my $not_found_in_foreign_clients_db = 0;
1144 my $local_address;
1145 my $local_mac;
1146 my ($target_ip, $target_port) = split(':', $target);
1148 # Determine the local ip address if target is an ip address
1149 if ($target =~ /^\d+\.\d+\.\d+\.\d+:\d+$/) {
1150 $local_address = &get_local_ip_for_remote_ip($target_ip).":$server_port";
1151 } else {
1152 $local_address = $server_address;
1153 }
1155 # Determine the local mac address if target is a mac address
1156 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) {
1157 my $loc_ip = &get_local_ip_for_remote_ip($heap->{'remote_ip'});
1158 my $network_interface= &get_interface_for_ip($loc_ip);
1159 $local_mac = &get_mac_for_interface($network_interface);
1160 } else {
1161 $local_mac = $server_mac_address;
1162 }
1164 # target and source is equal to GOSA -> process here
1165 if (not $done) {
1166 if ($target eq "GOSA" && $source eq "GOSA") {
1167 $done = 1;
1168 &daemon_log("$session_id DEBUG: target and source is 'GOSA' -> process here", 7);
1169 }
1170 }
1172 # target is own address without forward_to_gosa-tag -> process here
1173 if (not $done) {
1174 #if ((($target eq $local_address) || ($target eq $local_mac) ) && (not exists $msg_hash->{'forward_to_gosa'})) {
1175 if (($target eq $local_address) && (not exists $msg_hash->{'forward_to_gosa'})) {
1176 $done = 1;
1177 if ($source eq "GOSA") {
1178 $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1179 }
1180 &daemon_log("$session_id DEBUG: target is own address without forward_to_gosa-tag -> process here", 7);
1181 }
1182 }
1184 # target is a client address in known_clients -> process here
1185 if (not $done) {
1186 $sql = "SELECT * FROM $known_clients_tn WHERE (hostname='$target' OR macaddress LIKE '$target')";
1187 $res = $known_clients_db->select_dbentry($sql);
1188 if (keys(%$res) > 0) {
1189 $done = 1;
1190 my $hostname = $res->{1}->{'hostname'};
1191 $msg =~ s/<target>$target<\/target>/<target>$hostname<\/target>/;
1192 my $local_address = &get_local_ip_for_remote_ip($target_ip).":$server_port";
1193 if ($source eq "GOSA") {
1194 $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1195 }
1196 &daemon_log("$session_id DEBUG: target is a client address in known_clients -> process here", 7);
1198 } else {
1199 $not_found_in_known_clients_db = 1;
1200 }
1201 }
1203 # target ist own address with forward_to_gosa-tag not pointing to myself -> process here
1204 if (not $done) {
1205 my $forward_to_gosa = @{$msg_hash->{'forward_to_gosa'}}[0];
1206 my $gosa_at;
1207 my $gosa_session_id;
1208 if (($target eq $local_address) && (defined $forward_to_gosa)){
1209 my ($gosa_at, $gosa_session_id) = split(/,/, $forward_to_gosa);
1210 if ($gosa_at ne $local_address) {
1211 $done = 1;
1212 &daemon_log("$session_id DEBUG: target is own address with forward_to_gosa-tag not pointing to myself -> process here", 7);
1213 }
1214 }
1215 }
1217 # if message should be processed here -> add message to incoming_db
1218 if ($done) {
1219 # if a job or a gosa message comes from a foreign server, fake module to GosaPackages
1220 # so gosa-si-server knows how to process this kind of messages
1221 if ($header =~ /^gosa_/ || $header =~ /^job_/) {
1222 $module = "GosaPackages";
1223 }
1225 my $res = $incoming_db->add_dbentry( {table=>$incoming_tn,
1226 primkey=>[],
1227 headertag=>$header,
1228 targettag=>$target,
1229 xmlmessage=>&encode_base64($msg),
1230 timestamp=>&get_time,
1231 module=>$module,
1232 sessionid=>$session_id,
1233 } );
1235 }
1237 # target is own address with forward_to_gosa-tag pointing at myself -> forward to gosa
1238 if (not $done) {
1239 my $forward_to_gosa = @{$msg_hash->{'forward_to_gosa'}}[0];
1240 my $gosa_at;
1241 my $gosa_session_id;
1242 if (($target eq $local_address) && (defined $forward_to_gosa)){
1243 my ($gosa_at, $gosa_session_id) = split(/,/, $forward_to_gosa);
1244 if ($gosa_at eq $local_address) {
1245 my $session_reference = $kernel->ID_id_to_session($gosa_session_id);
1246 if( defined $session_reference ) {
1247 $heap = $session_reference->get_heap();
1248 }
1249 if(exists $heap->{'client'}) {
1250 $msg = &encrypt_msg($msg, $GosaPackages_key);
1251 $heap->{'client'}->put($msg);
1252 &daemon_log("$session_id INFO: incoming '$header' message forwarded to GOsa", 5);
1253 }
1254 $done = 1;
1255 &daemon_log("$session_id DEBUG: target is own address with forward_to_gosa-tag pointing at myself -> forward to gosa", 7);
1256 }
1257 }
1259 }
1261 # target is a client address in foreign_clients -> forward to registration server
1262 if (not $done) {
1263 $sql = "SELECT * FROM $foreign_clients_tn WHERE (hostname='$target' OR macaddress LIKE '$target')";
1264 $res = $foreign_clients_db->select_dbentry($sql);
1265 if (keys(%$res) > 0) {
1266 my $hostname = $res->{1}->{'hostname'};
1267 my ($host_ip, $host_port) = split(/:/, $hostname);
1268 my $local_address = &get_local_ip_for_remote_ip($host_ip).":$server_port";
1269 my $regserver = $res->{1}->{'regserver'};
1270 my $sql = "SELECT * FROM $known_server_tn WHERE hostname='$regserver'";
1271 my $res = $known_server_db->select_dbentry($sql);
1272 if (keys(%$res) > 0) {
1273 my $regserver_key = $res->{1}->{'hostkey'};
1274 $msg =~ s/<source>GOSA<\/source>/<source>$local_address<\/source>/;
1275 $msg =~ s/<target>$target<\/target>/<target>$hostname<\/target>/;
1276 if ($source eq "GOSA") {
1277 $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1278 }
1279 &send_msg_to_target($msg, $regserver, $regserver_key, $header, $session_id);
1280 }
1281 $done = 1;
1282 &daemon_log("$session_id DEBUG: target is a client address in foreign_clients -> forward to registration server", 7);
1283 } else {
1284 $not_found_in_foreign_clients_db = 1;
1285 }
1286 }
1288 # target is a server address -> forward to server
1289 if (not $done) {
1290 $sql = "SELECT * FROM $known_server_tn WHERE (hostname='$target' OR macaddress LIKE '$target')";
1291 $res = $known_server_db->select_dbentry($sql);
1292 if (keys(%$res) > 0) {
1293 my $hostkey = $res->{1}->{'hostkey'};
1295 if ($source eq "GOSA") {
1296 $msg =~ s/<source>GOSA<\/source>/<source>$local_address<\/source>/;
1297 $msg =~ s/<\/xml>/<forward_to_gosa>$local_address,$session_id<\/forward_to_gosa><\/xml>/;
1299 }
1301 &send_msg_to_target($msg, $target, $hostkey, $header, $session_id);
1302 $done = 1;
1303 &daemon_log("$session_id DEBUG: target is a server address -> forward to server", 7);
1304 } else {
1305 $not_found_in_known_server_db = 1;
1306 }
1307 }
1310 # target is not in foreign_clients_db, known_server_db or known_clients_db, maybe it is a complete new one -> process here
1311 if ( $not_found_in_foreign_clients_db
1312 && $not_found_in_known_server_db
1313 && $not_found_in_known_clients_db) {
1314 &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);
1315 if ($header =~ /^gosa_/ || $header =~ /^job_/) {
1316 $module = "GosaPackages";
1317 }
1318 my $res = $incoming_db->add_dbentry( {table=>$incoming_tn,
1319 primkey=>[],
1320 headertag=>$header,
1321 targettag=>$target,
1322 xmlmessage=>&encode_base64($msg),
1323 timestamp=>&get_time,
1324 module=>$module,
1325 sessionid=>$session_id,
1326 } );
1327 $done = 1;
1328 }
1331 if (not $done) {
1332 daemon_log("$session_id ERROR: do not know what to do with this message: $msg", 1);
1333 if ($source eq "GOSA") {
1334 my %data = ('error_msg' => &encode_base64($msg), 'error_string' => "Do not know what to do with this message!");
1335 my $error_msg = &build_msg("error", $local_address, "GOSA", \%data );
1337 my $session_reference = $kernel->ID_id_to_session($session_id);
1338 if( defined $session_reference ) {
1339 $heap = $session_reference->get_heap();
1340 }
1341 if(exists $heap->{'client'}) {
1342 $error_msg = &encrypt_msg($error_msg, $GosaPackages_key);
1343 $heap->{'client'}->put($error_msg);
1344 }
1345 }
1346 }
1348 }
1350 return;
1351 }
1354 sub next_task {
1355 my ($session, $heap, $task) = @_[SESSION, HEAP, ARG0];
1356 my $running_task = POE::Wheel::Run->new(
1357 Program => sub { process_task($session, $heap, $task) },
1358 StdioFilter => POE::Filter::Reference->new(),
1359 StdoutEvent => "task_result",
1360 StderrEvent => "task_debug",
1361 CloseEvent => "task_done",
1362 );
1363 $heap->{task}->{ $running_task->ID } = $running_task;
1364 }
1366 sub handle_task_result {
1367 my ($kernel, $heap, $result) = @_[KERNEL, HEAP, ARG0];
1368 my $client_answer = $result->{'answer'};
1369 if( $client_answer =~ s/session_id=(\d+)$// ) {
1370 my $session_id = $1;
1371 if( defined $session_id ) {
1372 my $session_reference = $kernel->ID_id_to_session($session_id);
1373 if( defined $session_reference ) {
1374 $heap = $session_reference->get_heap();
1375 }
1376 }
1378 if(exists $heap->{'client'}) {
1379 $heap->{'client'}->put($client_answer);
1380 }
1381 }
1382 $kernel->sig(CHLD => "child_reap");
1383 }
1385 sub handle_task_debug {
1386 my $result = $_[ARG0];
1387 print STDERR "$result\n";
1388 }
1390 sub handle_task_done {
1391 my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
1392 delete $heap->{task}->{$task_id};
1393 }
1395 sub process_task {
1396 no strict "refs";
1397 #CHECK: Not @_[...]?
1398 my ($session, $heap, $task) = @_;
1399 my $error = 0;
1400 my $answer_l;
1401 my ($answer_header, @answer_target_l, $answer_source);
1402 my $client_answer = "";
1404 # prepare all variables needed to process message
1405 #my $msg = $task->{'xmlmessage'};
1406 my $msg = &decode_base64($task->{'xmlmessage'});
1407 my $incoming_id = $task->{'id'};
1408 my $module = $task->{'module'};
1409 my $header = $task->{'headertag'};
1410 my $session_id = $task->{'sessionid'};
1411 my $msg_hash;
1412 eval {
1413 $msg_hash = $xml->XMLin($msg, ForceArray=>1);
1414 };
1415 daemon_log("ERROR: XML failure '$@'") if ($@);
1416 my $source = @{$msg_hash->{'source'}}[0];
1418 # set timestamp of incoming client uptodate, so client will not
1419 # be deleted from known_clients because of expiration
1420 my $act_time = &get_time();
1421 my $sql = "UPDATE $known_clients_tn SET timestamp='$act_time' WHERE hostname='$source'";
1422 my $res = $known_clients_db->exec_statement($sql);
1424 ######################
1425 # process incoming msg
1426 if( $error == 0) {
1427 daemon_log("$session_id INFO: Incoming msg (session_id=$session_id) with header '".@{$msg_hash->{'header'}}[0]."'", 5);
1428 daemon_log("$session_id DEBUG: Processing module ".$module, 7);
1429 $answer_l = &{ $module."::process_incoming_msg" }($msg, $msg_hash, $session_id);
1431 if ( 0 < @{$answer_l} ) {
1432 my $answer_str = join("\n", @{$answer_l});
1433 while ($answer_str =~ /<header>(\w+)<\/header>/g) {
1434 daemon_log("$session_id INFO: got answer message with header '$1'", 5);
1435 }
1436 daemon_log("$session_id DEBUG: $module: got answer from module: \n".$answer_str,9);
1437 } else {
1438 daemon_log("$session_id DEBUG: $module: got no answer from module!" ,7);
1439 }
1441 }
1442 if( !$answer_l ) { $error++ };
1444 ########
1445 # answer
1446 if( $error == 0 ) {
1448 foreach my $answer ( @{$answer_l} ) {
1449 # check outgoing msg to xml validity
1450 my $answer_hash = &check_outgoing_xml_validity($answer, $session_id);
1451 if( not defined $answer_hash ) { next; }
1453 $answer_header = @{$answer_hash->{'header'}}[0];
1454 @answer_target_l = @{$answer_hash->{'target'}};
1455 $answer_source = @{$answer_hash->{'source'}}[0];
1457 # deliver msg to all targets
1458 foreach my $answer_target ( @answer_target_l ) {
1460 # targets of msg are all gosa-si-clients in known_clients_db
1461 if( $answer_target eq "*" ) {
1462 # answer is for all clients
1463 my $sql_statement= "SELECT * FROM known_clients";
1464 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1465 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1466 my $host_name = $hit->{hostname};
1467 my $host_key = $hit->{hostkey};
1468 my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1469 &update_jobdb_status_for_send_msgs($answer, $error);
1470 }
1471 }
1473 # targets of msg are all gosa-si-server in known_server_db
1474 elsif( $answer_target eq "KNOWN_SERVER" ) {
1475 # answer is for all server in known_server
1476 my $sql_statement= "SELECT * FROM $known_server_tn";
1477 my $query_res = $known_server_db->select_dbentry( $sql_statement );
1478 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1479 my $host_name = $hit->{hostname};
1480 my $host_key = $hit->{hostkey};
1481 $answer =~ s/<target>\S+<\/target>/<target>$host_name<\/target>/g;
1482 my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1483 &update_jobdb_status_for_send_msgs($answer, $error);
1484 }
1485 }
1487 # target of msg is GOsa
1488 elsif( $answer_target eq "GOSA" ) {
1489 my $session_id = ($1) if $answer =~ /<session_id>(\d+?)<\/session_id>/;
1490 my $add_on = "";
1491 if( defined $session_id ) {
1492 $add_on = ".session_id=$session_id";
1493 }
1494 # answer is for GOSA and has to returned to connected client
1495 my $gosa_answer = &encrypt_msg($answer, $GosaPackages_key);
1496 $client_answer = $gosa_answer.$add_on;
1497 }
1499 # target of msg is job queue at this host
1500 elsif( $answer_target eq "JOBDB") {
1501 $answer =~ /<header>(\S+)<\/header>/;
1502 my $header;
1503 if( defined $1 ) { $header = $1; }
1504 my $error = &send_msg_to_target($answer, $server_address, $GosaPackages_key, $header, $session_id);
1505 &update_jobdb_status_for_send_msgs($answer, $error);
1506 }
1508 # Target of msg is a mac address
1509 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 ) {
1510 daemon_log("$session_id INFO: target is mac address '$answer_target', looking for host in known_clients and foreign_clients", 5);
1512 # Looking for macaddress in known_clients
1513 my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$answer_target'";
1514 my $query_res = $known_clients_db->select_dbentry( $sql_statement );
1515 my $found_ip_flag = 0;
1516 while( my ($hit_num, $hit) = each %{ $query_res } ) {
1517 my $host_name = $hit->{hostname};
1518 my $host_key = $hit->{hostkey};
1519 $answer =~ s/$answer_target/$host_name/g;
1520 daemon_log("$session_id INFO: found host '$host_name', associated to '$answer_target'", 5);
1521 my $error = &send_msg_to_target($answer, $host_name, $host_key, $answer_header, $session_id);
1522 &update_jobdb_status_for_send_msgs($answer, $error);
1523 $found_ip_flag++ ;
1524 }
1526 # Looking for macaddress in foreign_clients
1527 if ($found_ip_flag == 0) {
1528 my $sql = "SELECT * FROM $foreign_clients_tn WHERE macaddress LIKE '$answer_target'";
1529 my $res = $foreign_clients_db->select_dbentry($sql);
1530 while( my ($hit_num, $hit) = each %{ $res } ) {
1531 my $host_name = $hit->{hostname};
1532 my $reg_server = $hit->{regserver};
1533 daemon_log("$session_id INFO: found host '$host_name' with mac '$answer_target', registered at '$reg_server'", 5);
1535 # Fetch key for reg_server
1536 my $reg_server_key;
1537 my $sql = "SELECT * FROM $known_server_tn WHERE hostname='$reg_server'";
1538 my $res = $known_server_db->select_dbentry($sql);
1539 if (exists $res->{1}) {
1540 $reg_server_key = $res->{1}->{'hostkey'};
1541 } else {
1542 daemon_log("$session_id ERROR: cannot find hostkey for '$host_name' in '$known_server_tn'", 1);
1543 daemon_log("$session_id ERROR: unable to forward answer to correct registration server, processing is aborted!", 1);
1544 $reg_server_key = undef;
1545 }
1547 # Send answer to server where client is registered
1548 if (defined $reg_server_key) {
1549 $answer =~ s/$answer_target/$host_name/g;
1550 my $error = &send_msg_to_target($answer, $reg_server, $reg_server_key, $answer_header, $session_id);
1551 &update_jobdb_status_for_send_msgs($answer, $error);
1552 $found_ip_flag++ ;
1553 }
1554 }
1555 }
1557 # No mac to ip matching found
1558 if( $found_ip_flag == 0) {
1559 daemon_log("$session_id WARNING: no host found in known_clients or foreign_clients with mac address '$answer_target'", 3);
1560 &reactivate_job_with_delay($session_id, $answer_target, $answer_header, 30);
1561 }
1563 # Answer is for one specific host
1564 } else {
1565 # get encrypt_key
1566 my $encrypt_key = &get_encrypt_key($answer_target);
1567 if( not defined $encrypt_key ) {
1568 # unknown target
1569 daemon_log("$session_id WARNING: unknown target '$answer_target'", 3);
1570 next;
1571 }
1572 my $error = &send_msg_to_target($answer, $answer_target, $encrypt_key, $answer_header,$session_id);
1573 &update_jobdb_status_for_send_msgs($answer, $error);
1574 }
1575 }
1576 }
1577 }
1579 my $filter = POE::Filter::Reference->new();
1580 my %result = (
1581 status => "seems ok to me",
1582 answer => $client_answer,
1583 );
1585 my $output = $filter->put( [ \%result ] );
1586 print @$output;
1589 }
1591 sub session_start {
1592 my ($kernel) = $_[KERNEL];
1593 $global_kernel = $kernel;
1594 $kernel->yield('register_at_foreign_servers');
1595 $kernel->yield('create_fai_server_db', $fai_server_tn );
1596 $kernel->yield('create_fai_release_db', $fai_release_tn );
1597 $kernel->yield('watch_for_next_tasks');
1598 $kernel->sig(USR1 => "sig_handler");
1599 $kernel->sig(USR2 => "recreate_packages_db");
1600 $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1601 $kernel->delay_set('watch_for_done_jobs', $job_queue_loop_delay);
1602 $kernel->delay_set('watch_for_modified_jobs', $modified_jobs_loop_delay);
1603 $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay);
1604 $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay);
1605 $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay);
1606 $kernel->delay_set('watch_for_old_known_clients', $job_queue_loop_delay);
1608 # Start opsi check
1609 if ($opsi_enabled eq "true") {
1610 $kernel->delay_set('watch_for_opsi_jobs', $job_queue_opsi_delay);
1611 }
1613 }
1616 sub watch_for_done_jobs {
1617 #CHECK: $heap for what?
1618 my ($kernel,$heap) = @_[KERNEL, HEAP];
1620 my $sql_statement = "SELECT * FROM ".$job_queue_tn." WHERE ((status='done') AND (modified='0'))";
1621 my $res = $job_db->select_dbentry( $sql_statement );
1623 while( my ($id, $hit) = each %{$res} ) {
1624 my $jobdb_id = $hit->{id};
1625 my $sql_statement = "DELETE FROM $job_queue_tn WHERE id=$jobdb_id";
1626 my $res = $job_db->del_dbentry($sql_statement);
1627 }
1629 $kernel->delay_set('watch_for_done_jobs',$job_queue_loop_delay);
1630 }
1633 sub watch_for_opsi_jobs {
1634 my ($kernel) = $_[KERNEL];
1636 # 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
1637 # opsi install job is to parse the xml message. There is still the correct header.
1638 my $sql_statement = "SELECT * FROM ".$job_queue_tn." WHERE ((xmlmessage LIKE '%opsi_install_client</header>%') AND (status='processing') AND (siserver='localhost'))";
1639 my $res = $job_db->select_dbentry( $sql_statement );
1641 # Ask OPSI for an update of the running jobs
1642 while (my ($id, $hit) = each %$res ) {
1643 # Determine current parameters of the job
1644 my $hostId = $hit->{'plainname'};
1645 my $macaddress = $hit->{'macaddress'};
1646 my $progress = $hit->{'progress'};
1648 my $result= {};
1650 # For hosts, only return the products that are or get installed
1651 my $callobj;
1652 $callobj = {
1653 method => 'getProductStates_hash',
1654 params => [ $hostId ],
1655 id => 1,
1656 };
1658 my $hres = $opsi_client->call($opsi_url, $callobj);
1659 #my ($hres_err, $hres_err_string) = &check_opsi_res($hres);
1660 if (not &check_opsi_res($hres)) {
1661 my $htmp= $hres->result->{$hostId};
1663 # Check state != not_installed or action == setup -> load and add
1664 my $products= 0;
1665 my $installed= 0;
1666 my $installing = 0;
1667 my $error= 0;
1668 my @installed_list;
1669 my @error_list;
1670 my $act_status = "none";
1671 foreach my $product (@{$htmp}){
1673 if ($product->{'installationStatus'} ne "not_installed" or
1674 $product->{'actionRequest'} eq "setup"){
1676 # Increase number of products for this host
1677 $products++;
1679 if ($product->{'installationStatus'} eq "failed"){
1680 $result->{$product->{'productId'}}= "error";
1681 unshift(@error_list, $product->{'productId'});
1682 $error++;
1683 }
1684 if ($product->{'installationStatus'} eq "installed" && $product->{'actionRequest'} eq "none"){
1685 $result->{$product->{'productId'}}= "installed";
1686 unshift(@installed_list, $product->{'productId'});
1687 $installed++;
1688 }
1689 if ($product->{'installationStatus'} eq "installing"){
1690 $result->{$product->{'productId'}}= "installing";
1691 $installing++;
1692 $act_status = "installing - ".$product->{'productId'};
1693 }
1694 }
1695 }
1697 # Estimate "rough" progress, avoid division by zero
1698 if ($products == 0) {
1699 $result->{'progress'}= 0;
1700 } else {
1701 $result->{'progress'}= int($installed * 100 / $products);
1702 }
1704 # Set updates in job queue
1705 if ((not $error) && (not $installing) && ($installed)) {
1706 $act_status = "installed - ".join(", ", @installed_list);
1707 }
1708 if ($error) {
1709 $act_status = "error - ".join(", ", @error_list);
1710 }
1711 if ($progress ne $result->{'progress'} ) {
1712 # Updating progress and result
1713 my $update_statement = "UPDATE $job_queue_tn SET modified='1', progress='".$result->{'progress'}."', result='$act_status' WHERE macaddress='$macaddress' AND siserver='localhost'";
1714 my $update_res = $job_db->update_dbentry($update_statement);
1715 }
1716 if ($progress eq 100) {
1717 # Updateing status
1718 my $done_statement = "UPDATE $job_queue_tn SET modified='1', ";
1719 if ($error) {
1720 $done_statement .= "status='error'";
1721 } else {
1722 $done_statement .= "status='done'";
1723 }
1724 $done_statement .= " WHERE macaddress='$macaddress' AND siserver='localhost'";
1725 my $done_res = $job_db->update_dbentry($done_statement);
1726 }
1729 }
1730 }
1732 $kernel->delay_set('watch_for_opsi_jobs', $job_queue_opsi_delay);
1733 }
1736 # If a job got an update or was modified anyway, send to all other si-server an update message of this jobs.
1737 sub watch_for_modified_jobs {
1738 my ($kernel,$heap) = @_[KERNEL, HEAP];
1740 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE (modified='1')";
1741 my $res = $job_db->select_dbentry( $sql_statement );
1743 # if db contains no jobs which should be update, do nothing
1744 if (keys %$res != 0) {
1746 if ($job_synchronization eq "true") {
1747 # make out of the db result a gosa-si message
1748 my $update_msg = &db_res2si_msg ($res, "foreign_job_updates", "KNOWN_SERVER", "MY_LOCAL_ADDRESS");
1750 # update all other SI-server
1751 &inform_all_other_si_server($update_msg);
1752 }
1754 # set jobs all jobs to modified = 0, wait until the next modification for updates of other si-server
1755 $sql_statement = "UPDATE $job_queue_tn SET modified='0' ";
1756 $res = $job_db->update_dbentry($sql_statement);
1757 }
1759 $kernel->delay_set('watch_for_modified_jobs', $modified_jobs_loop_delay);
1760 }
1763 sub watch_for_new_jobs {
1764 if($watch_for_new_jobs_in_progress == 0) {
1765 $watch_for_new_jobs_in_progress = 1;
1766 my ($kernel,$heap) = @_[KERNEL, HEAP];
1768 # check gosa job quaeue for jobs with executable timestamp
1769 my $timestamp = &get_time();
1770 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE status='waiting' AND (CAST(timestamp AS UNSIGNED)) < $timestamp ORDER BY timestamp";
1771 my $res = $job_db->exec_statement( $sql_statement );
1773 # Merge all new jobs that would do the same actions
1774 my @drops;
1775 my $hits;
1776 foreach my $hit (reverse @{$res} ) {
1777 my $macaddress= lc @{$hit}[8];
1778 my $headertag= @{$hit}[5];
1779 if(
1780 defined($hits->{$macaddress}) &&
1781 defined($hits->{$macaddress}->{$headertag}) &&
1782 defined($hits->{$macaddress}->{$headertag}[0])
1783 ) {
1784 push @drops, "DELETE FROM $job_queue_tn WHERE id = $hits->{$macaddress}->{$headertag}[0]";
1785 }
1786 $hits->{$macaddress}->{$headertag}= $hit;
1787 }
1789 # Delete new jobs with a matching job in state 'processing'
1790 foreach my $macaddress (keys %{$hits}) {
1791 foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1792 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1793 if(defined($jobdb_id)) {
1794 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND headertag='$jobdb_headertag' AND status='processing'";
1795 my $res = $job_db->exec_statement( $sql_statement );
1796 foreach my $hit (@{$res}) {
1797 push @drops, "DELETE FROM $job_queue_tn WHERE id=$jobdb_id";
1798 }
1799 } else {
1800 daemon_log("J ERROR: Job without id exists for macaddress $macaddress!", 1);
1801 }
1802 }
1803 }
1805 # Commit deletion
1806 $job_db->exec_statementlist(\@drops);
1808 # Look for new jobs that could be executed
1809 foreach my $macaddress (keys %{$hits}) {
1811 # Look if there is an executing job
1812 my $sql_statement = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND status='processing'";
1813 my $res = $job_db->exec_statement( $sql_statement );
1815 # Skip new jobs for host if there is a processing job
1816 if(defined($res) and defined @{$res}[0]) {
1817 # Prevent race condition if there is a trigger_activate job waiting and a goto-activation job processing
1818 my $row = @{$res}[0] if (ref $res eq 'ARRAY');
1819 if(@{$row}[5] eq 'trigger_action_reinstall') {
1820 my $sql_statement_2 = "SELECT * FROM $job_queue_tn WHERE macaddress LIKE '$macaddress' AND status='waiting' AND headertag = 'trigger_activate_new'";
1821 my $res_2 = $job_db->exec_statement( $sql_statement_2 );
1822 if(defined($res_2) and defined @{$res_2}[0]) {
1823 # Set status from goto-activation to 'waiting' and update timestamp
1824 $job_db->exec_statement("UPDATE $job_queue_tn SET status='waiting' WHERE macaddress LIKE '$macaddress' AND headertag = 'trigger_action_reinstall'");
1825 $job_db->exec_statement("UPDATE $job_queue_tn SET timestamp='".&get_time(30)."' WHERE macaddress LIKE '$macaddress' AND headertag = 'trigger_action_reinstall'");
1826 }
1827 }
1828 next;
1829 }
1831 foreach my $jobdb_headertag (keys %{$hits->{$macaddress}}) {
1832 my $jobdb_id = @{$hits->{$macaddress}->{$jobdb_headertag}}[0];
1833 if(defined($jobdb_id)) {
1834 my $job_msg = @{$hits->{$macaddress}->{$jobdb_headertag}}[7];
1836 daemon_log("J DEBUG: its time to execute $job_msg", 7);
1837 my $sql_statement = "SELECT * FROM known_clients WHERE macaddress LIKE '$macaddress'";
1838 my $res_hash = $known_clients_db->select_dbentry( $sql_statement );
1840 # expect macaddress is unique!!!!!!
1841 my $target = $res_hash->{1}->{hostname};
1843 # change header
1844 $job_msg =~ s/<header>job_/<header>gosa_/;
1846 # add sqlite_id
1847 $job_msg =~ s/<\/xml>$/<jobdb_id>$jobdb_id<\/jobdb_id><\/xml>/;
1849 $job_msg =~ /<header>(\S+)<\/header>/;
1850 my $header = $1 ;
1851 my $func_error = &send_msg_to_target($job_msg, $server_address, $GosaPackages_key, $header, "J");
1853 # update status in job queue to ...
1854 # ... 'processing', for jobs: 'reinstall', 'update'
1855 if (($header =~ /gosa_trigger_action_reinstall/)
1856 || ($header =~ /gosa_trigger_activate_new/)
1857 || ($header =~ /gosa_trigger_action_update/)) {
1858 my $sql_statement = "UPDATE $job_queue_tn SET status='processing' WHERE id=$jobdb_id";
1859 my $dbres = $job_db->update_dbentry($sql_statement);
1860 }
1862 # ... 'done', for all other jobs, they are no longer needed in the jobqueue
1863 else {
1864 my $sql_statement = "UPDATE $job_queue_tn SET status='done' WHERE id=$jobdb_id";
1865 my $dbres = $job_db->update_dbentry($sql_statement);
1866 }
1869 # We don't want parallel processing
1870 last;
1871 }
1872 }
1873 }
1875 $watch_for_new_jobs_in_progress = 0;
1876 $kernel->delay_set('watch_for_new_jobs', $job_queue_loop_delay);
1877 }
1878 }
1881 sub watch_for_new_messages {
1882 my ($kernel,$heap) = @_[KERNEL, HEAP];
1883 my @coll_user_msg; # collection list of outgoing messages
1885 # check messaging_db for new incoming messages with executable timestamp
1886 my $timestamp = &get_time();
1887 my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( (CAST(timestamp AS UNSIGNED))<$timestamp AND flag='n' AND direction='in' )";
1888 my $res = $messaging_db->exec_statement( $sql_statement );
1889 foreach my $hit (@{$res}) {
1891 # create outgoing messages
1892 my $message_to = @{$hit}[3];
1893 # translate message_to to plain login name
1894 my @message_to_l = split(/,/, $message_to);
1895 my %receiver_h;
1896 foreach my $receiver (@message_to_l) {
1897 if ($receiver =~ /^u_([\s\S]*)$/) {
1898 $receiver_h{$receiver} = 0;
1899 } elsif ($receiver =~ /^g_([\s\S]*)$/) {
1900 my $group_name = $1;
1901 # fetch all group members from ldap and add them to receiver hash
1902 my $ldap_handle = &get_ldap_handle();
1903 if (defined $ldap_handle) {
1904 my $mesg = $ldap_handle->search(
1905 base => $ldap_base,
1906 scope => 'sub',
1907 attrs => ['memberUid'],
1908 filter => "cn=$group_name",
1909 );
1910 if ($mesg->count) {
1911 my @entries = $mesg->entries;
1912 foreach my $entry (@entries) {
1913 my @receivers= $entry->get_value("memberUid");
1914 foreach my $receiver (@receivers) {
1915 $receiver_h{$receiver} = 0;
1916 }
1917 }
1918 }
1919 # translating errors ?
1920 if ($mesg->code) {
1921 daemon_log("M ERROR: unable to translate group '$group_name' to user list for message delivery: $mesg->error", 1);
1922 }
1923 # ldap handle error ?
1924 } else {
1925 daemon_log("M ERROR: unable to translate group '$group_name' to user list for message delivery: no ldap handle available", 1);
1926 }
1927 } else {
1928 my $sbjct = &encode_base64(@{$hit}[1]);
1929 my $msg = &encode_base64(@{$hit}[7]);
1930 &daemon_log("M WARNING: unknown receiver '$receiver' for a user-message '$sbjct - $msg'", 3);
1931 }
1932 }
1933 my @receiver_l = keys(%receiver_h);
1935 my $message_id = @{$hit}[0];
1937 #add each outgoing msg to messaging_db
1938 my $receiver;
1939 foreach $receiver (@receiver_l) {
1940 my $sql_statement = "INSERT INTO $messaging_tn (id, subject, message_from, message_to, flag, direction, delivery_time, message, timestamp) ".
1941 "VALUES ('".
1942 $message_id."', '". # id
1943 @{$hit}[1]."', '". # subject
1944 @{$hit}[2]."', '". # message_from
1945 $receiver."', '". # message_to
1946 "none"."', '". # flag
1947 "out"."', '". # direction
1948 @{$hit}[6]."', '". # delivery_time
1949 @{$hit}[7]."', '". # message
1950 $timestamp."'". # timestamp
1951 ")";
1952 &daemon_log("M DEBUG: $sql_statement", 1);
1953 my $res = $messaging_db->exec_statement($sql_statement);
1954 &daemon_log("M INFO: message '".@{$hit}[0]."' is prepared for delivery to receiver '$receiver'", 5);
1955 }
1957 # set incoming message to flag d=deliverd
1958 $sql_statement = "UPDATE $messaging_tn SET flag='p' WHERE id='$message_id'";
1959 &daemon_log("M DEBUG: $sql_statement", 7);
1960 $res = $messaging_db->update_dbentry($sql_statement);
1961 &daemon_log("M INFO: message '$message_id' is set to flag 'p' (processed)", 5);
1962 }
1964 $kernel->delay_set('watch_for_new_messages', $messaging_db_loop_delay);
1965 return;
1966 }
1968 sub watch_for_delivery_messages {
1969 my ($kernel, $heap) = @_[KERNEL, HEAP];
1971 # select outgoing messages
1972 my $sql_statement = "SELECT * FROM $messaging_tn WHERE ( flag='p' AND direction='out' )";
1973 #&daemon_log("0 DEBUG: $sql", 7);
1974 my $res = $messaging_db->exec_statement( $sql_statement );
1976 # build out msg for each usr
1977 foreach my $hit (@{$res}) {
1978 my $receiver = @{$hit}[3];
1979 my $msg_id = @{$hit}[0];
1980 my $subject = @{$hit}[1];
1981 my $message = @{$hit}[7];
1983 # resolve usr -> host where usr is logged in
1984 my $sql = "SELECT * FROM $login_users_tn WHERE (user='$receiver')";
1985 #&daemon_log("0 DEBUG: $sql", 7);
1986 my $res = $login_users_db->exec_statement($sql);
1988 # receiver is logged in nowhere
1989 if (not ref(@$res[0]) eq "ARRAY") { next; }
1991 # receiver ist logged in at a client registered at local server
1992 my $send_succeed = 0;
1993 foreach my $hit (@$res) {
1994 my $receiver_host = @$hit[0];
1995 my $delivered2host = 0;
1996 &daemon_log("M DEBUG: user '$receiver' is logged in at host '$receiver_host'", 7);
1998 # Looking for host in know_clients_db
1999 my $sql = "SELECT * FROM $known_clients_tn WHERE (hostname='$receiver_host')";
2000 my $res = $known_clients_db->exec_statement($sql);
2002 # Host is known in known_clients_db
2003 if (ref(@$res[0]) eq "ARRAY") {
2004 my $receiver_key = @{@{$res}[0]}[2];
2005 my %data = ('subject' => $subject, 'message' => $message, 'usr' => $receiver);
2006 my $out_msg = &build_msg("usr_msg", $server_address, $receiver_host, \%data );
2007 my $error = &send_msg_to_target($out_msg, $receiver_host, $receiver_key, "usr_msg", 0);
2008 if ($error == 0 ) {
2009 $send_succeed++ ;
2010 $delivered2host++ ;
2011 &daemon_log("M DEBUG: send message for user '$receiver' to host '$receiver_host'", 7);
2012 } else {
2013 &daemon_log("M DEBUG: cannot send message for user '$receiver' to host '$receiver_host'", 7);
2014 }
2015 }
2017 # Message already send, do not need to do anything more, otherwise ...
2018 if ($delivered2host) { next;}
2020 # ...looking for host in foreign_clients_db
2021 $sql = "SELECT * FROM $foreign_clients_tn WHERE (hostname='$receiver_host')";
2022 $res = $foreign_clients_db->exec_statement($sql);
2024 # Host is known in foreign_clients_db
2025 if (ref(@$res[0]) eq "ARRAY") {
2026 my $registration_server = @{@{$res}[0]}[2];
2028 # Fetch encryption key for registration server
2029 my $sql = "SELECT * FROM $known_server_tn WHERE (hostname='$registration_server')";
2030 my $res = $known_server_db->exec_statement($sql);
2031 if (ref(@$res[0]) eq "ARRAY") {
2032 my $registration_server_key = @{@{$res}[0]}[3];
2033 my %data = ('subject' => $subject, 'message' => $message, 'usr' => $receiver);
2034 my $out_msg = &build_msg("usr_msg", $server_address, $receiver_host, \%data );
2035 my $error = &send_msg_to_target($out_msg, $registration_server, $registration_server_key, "usr_msg", 0);
2036 if ($error == 0 ) {
2037 $send_succeed++ ;
2038 $delivered2host++ ;
2039 &daemon_log("M DEBUG: send message for user '$receiver' to server '$registration_server'", 7);
2040 } else {
2041 &daemon_log("M ERROR: cannot send message for user '$receiver' to server '$registration_server'", 1);
2042 }
2044 } else {
2045 &daemon_log("M ERROR: host '$receiver_host' is reported to be ".
2046 "registrated at server '$registration_server', ".
2047 "but no data available in known_server_db ", 1);
2048 }
2049 }
2051 if (not $delivered2host) {
2052 &daemon_log("M ERROR: unable to send user message to host '$receiver_host'", 1);
2053 }
2054 }
2056 if ($send_succeed) {
2057 # set outgoing msg at db to deliverd
2058 my $sql = "UPDATE $messaging_tn SET flag='d' WHERE (id='$msg_id' AND direction='out' AND message_to='$receiver')";
2059 my $res = $messaging_db->exec_statement($sql);
2060 &daemon_log("M INFO: send message for user '$receiver' to logged in hosts", 5);
2061 } else {
2062 &daemon_log("M WARNING: failed to deliver message for user '$receiver'", 3);
2063 }
2064 }
2066 $kernel->delay_set('watch_for_delivery_messages', $messaging_db_loop_delay);
2067 return;
2068 }
2071 sub watch_for_done_messages {
2072 my ($kernel,$heap) = @_[KERNEL, HEAP];
2074 my $sql = "SELECT * FROM $messaging_tn WHERE (flag='p' AND direction='in')";
2075 #&daemon_log("0 DEBUG: $sql", 7);
2076 my $res = $messaging_db->exec_statement($sql);
2078 foreach my $hit (@{$res}) {
2079 my $msg_id = @{$hit}[0];
2081 my $sql = "SELECT * FROM $messaging_tn WHERE (id='$msg_id' AND direction='out' AND (NOT flag='s'))";
2082 #&daemon_log("0 DEBUG: $sql", 7);
2083 my $res = $messaging_db->exec_statement($sql);
2085 # not all usr msgs have been seen till now
2086 if ( ref(@$res[0]) eq "ARRAY") { next; }
2088 $sql = "DELETE FROM $messaging_tn WHERE (id='$msg_id')";
2089 #&daemon_log("0 DEBUG: $sql", 7);
2090 $res = $messaging_db->exec_statement($sql);
2092 }
2094 $kernel->delay_set('watch_for_done_messages', $messaging_db_loop_delay);
2095 return;
2096 }
2099 sub watch_for_old_known_clients {
2100 my ($kernel,$heap) = @_[KERNEL, HEAP];
2102 my $sql_statement = "SELECT * FROM $known_clients_tn";
2103 my $res = $known_clients_db->select_dbentry( $sql_statement );
2105 my $act_time = int(&get_time());
2107 while ( my ($hit_num, $hit) = each %$res) {
2108 my $expired_timestamp = int($hit->{'timestamp'});
2109 $expired_timestamp =~ /(\d{4})(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)/;
2110 my $dt = DateTime->new( year => $1,
2111 month => $2,
2112 day => $3,
2113 hour => $4,
2114 minute => $5,
2115 second => $6,
2116 );
2118 $dt->add( seconds => 2 * int($hit->{'keylifetime'}) );
2119 $expired_timestamp = $dt->ymd('').$dt->hms('');
2120 if ($act_time > $expired_timestamp) {
2121 my $hostname = $hit->{'hostname'};
2122 my $del_sql = "DELETE FROM $known_clients_tn WHERE hostname='$hostname'";
2123 my $del_res = $known_clients_db->exec_statement($del_sql);
2125 &main::daemon_log("0 INFO: timestamp '".$hit->{'timestamp'}."' of client '$hostname' is expired('$expired_timestamp'), client will be deleted from known_clients_db", 5);
2126 }
2128 }
2130 $kernel->delay_set('watch_for_old_known_clients', $job_queue_loop_delay);
2131 }
2134 sub watch_for_next_tasks {
2135 my ($kernel,$heap) = @_[KERNEL, HEAP];
2137 my $sql = "SELECT * FROM $incoming_tn";
2138 my $res = $incoming_db->select_dbentry($sql);
2140 while ( my ($hit_num, $hit) = each %$res) {
2141 my $headertag = $hit->{'headertag'};
2142 if ($headertag =~ /^answer_(\d+)/) {
2143 # do not start processing, this message is for a still running POE::Wheel
2144 next;
2145 }
2146 my $message_id = $hit->{'id'};
2147 my $session_id = $hit->{'sessionid'};
2148 &daemon_log("$session_id DEBUG: start processing for message with incoming id: '$message_id'", 7);
2149 $kernel->yield('next_task', $hit);
2151 my $sql = "DELETE FROM $incoming_tn WHERE id=$message_id";
2152 my $res = $incoming_db->exec_statement($sql);
2153 }
2155 $kernel->delay_set('watch_for_next_tasks', 1);
2156 }
2159 sub get_ldap_handle {
2160 my ($session_id) = @_;
2161 my $heap;
2162 my $ldap_handle;
2164 if (not defined $session_id ) { $session_id = 0 };
2165 if ($session_id =~ /[^0-9]*/) { $session_id = 0 };
2167 if ($session_id == 0) {
2168 daemon_log("$session_id DEBUG: get_ldap_handle invoked without a session_id, create a new ldap_handle", 7);
2169 $ldap_handle = Net::LDAP->new( $ldap_uri );
2170 if (defined $ldap_handle) {
2171 $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!");
2172 } else {
2173 daemon_log("$session_id ERROR: creation of a new LDAP handle failed (ldap_uri '$ldap_uri')");
2174 }
2176 } else {
2177 my $session_reference = $global_kernel->ID_id_to_session($session_id);
2178 if( defined $session_reference ) {
2179 $heap = $session_reference->get_heap();
2180 }
2182 if (not defined $heap) {
2183 daemon_log("$session_id DEBUG: cannot get heap for session_id '$session_id'", 7);
2184 return;
2185 }
2187 # TODO: This "if" is nonsense, because it doesn't prove that the
2188 # used handle is still valid - or if we've to reconnect...
2189 #if (not exists $heap->{ldap_handle}) {
2190 $ldap_handle = Net::LDAP->new( $ldap_uri );
2191 $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!");
2192 $heap->{ldap_handle} = $ldap_handle;
2193 #}
2194 }
2195 return $ldap_handle;
2196 }
2199 sub change_fai_state {
2200 my ($st, $targets, $session_id) = @_;
2201 $session_id = 0 if not defined $session_id;
2202 # Set FAI state to localboot
2203 my %mapActions= (
2204 reboot => '',
2205 update => 'softupdate',
2206 localboot => 'localboot',
2207 reinstall => 'install',
2208 rescan => '',
2209 wake => '',
2210 memcheck => 'memcheck',
2211 sysinfo => 'sysinfo',
2212 install => 'install',
2213 );
2215 # Return if this is unknown
2216 if (!exists $mapActions{ $st }){
2217 daemon_log("$session_id ERROR: unknown action '$st', can not translate ot FAIstate", 1);
2218 return;
2219 }
2221 my $state= $mapActions{ $st };
2223 my $ldap_handle = &get_ldap_handle($session_id);
2224 if( defined($ldap_handle) ) {
2226 # Build search filter for hosts
2227 my $search= "(&(objectClass=GOhard)";
2228 foreach (@{$targets}){
2229 $search.= "(macAddress=$_)";
2230 }
2231 $search.= ")";
2233 # If there's any host inside of the search string, procress them
2234 if (!($search =~ /macAddress/)){
2235 daemon_log("$session_id ERROR: no macAddress found in filter statement for LDAP search: '$search'", 1);
2236 return;
2237 }
2239 # Perform search for Unit Tag
2240 my $mesg = $ldap_handle->search(
2241 base => $ldap_base,
2242 scope => 'sub',
2243 attrs => ['dn', 'FAIstate', 'objectClass'],
2244 filter => "$search"
2245 );
2247 if ($mesg->count) {
2248 my @entries = $mesg->entries;
2249 if (0 == @entries) {
2250 daemon_log("$session_id ERROR: ldap search failed: ldap_base=$ldap_base, filter=$search", 1);
2251 }
2253 foreach my $entry (@entries) {
2254 # Only modify entry if it is not set to '$state'
2255 if ($entry->get_value("FAIstate") ne "$state"){
2256 daemon_log("$session_id INFO: Setting FAIstate to '$state' for ".$entry->dn, 5);
2257 my $result;
2258 my %tmp = map { $_ => 1 } $entry->get_value("objectClass");
2259 if (exists $tmp{'FAIobject'}){
2260 if ($state eq ''){
2261 $result= $ldap_handle->modify($entry->dn, changes => [ delete => [ FAIstate => [] ] ]);
2262 } else {
2263 $result= $ldap_handle->modify($entry->dn, changes => [ replace => [ FAIstate => $state ] ]);
2264 }
2265 } elsif ($state ne ''){
2266 $result= $ldap_handle->modify($entry->dn, changes => [ add => [ objectClass => 'FAIobject' ], add => [ FAIstate => $state ] ]);
2267 }
2269 # Errors?
2270 if ($result->code){
2271 daemon_log("$session_id Error: Setting FAIstate to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
2272 }
2273 } else {
2274 daemon_log("$session_id DEBUG FAIstate at host '".$entry->dn."' already at state '$st'", 7);
2275 }
2276 }
2277 } else {
2278 daemon_log("$session_id ERROR: LDAP search failed: ldap_base=$ldap_base, filter=$search", 1);
2279 }
2281 # if no ldap handle defined
2282 } else {
2283 daemon_log("$session_id ERROR: no LDAP handle defined for update FAIstate", 1);
2284 }
2286 return;
2287 }
2290 sub change_goto_state {
2291 my ($st, $targets, $session_id) = @_;
2292 $session_id = 0 if not defined $session_id;
2294 # Switch on or off?
2295 my $state= $st eq 'active' ? 'active': 'locked';
2297 my $ldap_handle = &get_ldap_handle($session_id);
2298 if( defined($ldap_handle) ) {
2300 # Build search filter for hosts
2301 my $search= "(&(objectClass=GOhard)";
2302 foreach (@{$targets}){
2303 $search.= "(macAddress=$_)";
2304 }
2305 $search.= ")";
2307 # If there's any host inside of the search string, procress them
2308 if (!($search =~ /macAddress/)){
2309 return;
2310 }
2312 # Perform search for Unit Tag
2313 my $mesg = $ldap_handle->search(
2314 base => $ldap_base,
2315 scope => 'sub',
2316 attrs => ['dn', 'gotoMode'],
2317 filter => "$search"
2318 );
2320 if ($mesg->count) {
2321 my @entries = $mesg->entries;
2322 foreach my $entry (@entries) {
2324 # Only modify entry if it is not set to '$state'
2325 if ($entry->get_value("gotoMode") ne $state){
2327 daemon_log("$session_id INFO: Setting gotoMode to '$state' for ".$entry->dn, 5);
2328 my $result;
2329 $result= $ldap_handle->modify($entry->dn, changes => [replace => [ gotoMode => $state ] ]);
2331 # Errors?
2332 if ($result->code){
2333 &daemon_log("$session_id Error: Setting gotoMode to '$state' for ".$entry->dn. "failed: ".$result->error, 1);
2334 }
2336 }
2337 }
2338 } else {
2339 daemon_log("$session_id ERROR: LDAP search failed in function change_goto_state: ldap_base=$ldap_base, filter=$search", 1);
2340 }
2342 }
2343 }
2346 sub run_recreate_packages_db {
2347 my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
2348 my $session_id = $session->ID;
2349 &main::daemon_log("$session_id INFO: Recreating FAI Packages DB ('$fai_release_tn', '$fai_server_tn', '$packages_list_tn')", 5);
2350 $kernel->yield('create_fai_release_db', $fai_release_tn);
2351 $kernel->yield('create_fai_server_db', $fai_server_tn);
2352 return;
2353 }
2356 sub run_create_fai_server_db {
2357 my ($kernel, $session, $heap, $table_name) = @_[KERNEL, SESSION, HEAP, ARG0];
2358 my $session_id = $session->ID;
2359 my $task = POE::Wheel::Run->new(
2360 Program => sub { &create_fai_server_db($table_name,$kernel, undef, $session_id) },
2361 StdoutEvent => "session_run_result",
2362 StderrEvent => "session_run_debug",
2363 CloseEvent => "session_run_done",
2364 );
2366 $heap->{task}->{ $task->ID } = $task;
2367 return;
2368 }
2371 sub create_fai_server_db {
2372 my ($table_name, $kernel, $dont_create_packages_list, $session_id) = @_;
2373 my $result;
2375 if (not defined $session_id) { $session_id = 0; }
2376 my $ldap_handle = &get_ldap_handle();
2377 if(defined($ldap_handle)) {
2378 daemon_log("$session_id INFO: create_fai_server_db: start", 5);
2379 my $mesg= $ldap_handle->search(
2380 base => $ldap_base,
2381 scope => 'sub',
2382 attrs => ['FAIrepository', 'gosaUnitTag'],
2383 filter => "(&(FAIrepository=*)(objectClass=FAIrepositoryServer))",
2384 );
2385 if($mesg->{'resultCode'} == 0 &&
2386 $mesg->count != 0) {
2387 foreach my $entry (@{$mesg->{entries}}) {
2388 if($entry->exists('FAIrepository')) {
2389 # Add an entry for each Repository configured for server
2390 foreach my $repo(@{$entry->get_value('FAIrepository', asref => 1)}) {
2391 my($tmp_url,$tmp_server,$tmp_release,$tmp_sections) = split(/\|/, $repo);
2392 my $tmp_tag= $entry->get_value('gosaUnitTag') || "";
2393 $result= $fai_server_db->add_dbentry( {
2394 table => $table_name,
2395 primkey => ['server', 'fai_release', 'tag'],
2396 server => $tmp_url,
2397 fai_release => $tmp_release,
2398 sections => $tmp_sections,
2399 tag => (length($tmp_tag)>0)?$tmp_tag:"",
2400 } );
2401 }
2402 }
2403 }
2404 }
2405 daemon_log("$session_id INFO: create_fai_server_db: finished", 5);
2407 # TODO: Find a way to post the 'create_packages_list_db' event
2408 if(not defined($dont_create_packages_list)) {
2409 &create_packages_list_db(undef, undef, $session_id);
2410 }
2411 }
2413 $ldap_handle->disconnect;
2414 return $result;
2415 }
2418 sub run_create_fai_release_db {
2419 my ($session, $heap, $table_name) = @_[SESSION, HEAP, ARG0];
2420 my $session_id = $session->ID;
2421 my $task = POE::Wheel::Run->new(
2422 Program => sub { &create_fai_release_db($table_name, $session_id) },
2423 StdoutEvent => "session_run_result",
2424 StderrEvent => "session_run_debug",
2425 CloseEvent => "session_run_done",
2426 );
2428 $heap->{task}->{ $task->ID } = $task;
2429 return;
2430 }
2433 sub create_fai_release_db {
2434 my ($table_name, $session_id) = @_;
2435 my $result;
2437 # used for logging
2438 if (not defined $session_id) { $session_id = 0; }
2440 my $ldap_handle = &get_ldap_handle();
2441 if(defined($ldap_handle)) {
2442 daemon_log("$session_id INFO: create_fai_release_db: start",5);
2443 my $mesg= $ldap_handle->search(
2444 base => $ldap_base,
2445 scope => 'sub',
2446 attrs => [],
2447 filter => "(&(objectClass=organizationalUnit)(ou=fai))",
2448 );
2449 if($mesg->{'resultCode'} == 0 &&
2450 $mesg->count != 0) {
2451 # Walk through all possible FAI container ou's
2452 my @sql_list;
2453 my $timestamp= &get_time();
2454 foreach my $ou (@{$mesg->{entries}}) {
2455 my $tmp_classes= resolve_fai_classes($ou->dn, $ldap_handle, $session_id);
2456 if(defined($tmp_classes) && ref($tmp_classes) eq 'HASH') {
2457 my @tmp_array=get_fai_release_entries($tmp_classes);
2458 if(@tmp_array) {
2459 foreach my $entry (@tmp_array) {
2460 if(defined($entry) && ref($entry) eq 'HASH') {
2461 my $sql=
2462 "INSERT INTO $table_name "
2463 ."(timestamp, fai_release, class, type, state) VALUES ("
2464 .$timestamp.","
2465 ."'".$entry->{'release'}."',"
2466 ."'".$entry->{'class'}."',"
2467 ."'".$entry->{'type'}."',"
2468 ."'".$entry->{'state'}."')";
2469 push @sql_list, $sql;
2470 }
2471 }
2472 }
2473 }
2474 }
2476 daemon_log("$session_id DEBUG: Inserting ".scalar @sql_list." entries to DB",8);
2477 if(@sql_list) {
2478 unshift @sql_list, "DELETE FROM $table_name";
2479 $fai_release_db->exec_statementlist(\@sql_list);
2480 }
2481 daemon_log("$session_id DEBUG: Done with inserting",7);
2482 }
2483 daemon_log("$session_id INFO: create_fai_release_db: finished",5);
2484 }
2485 $ldap_handle->disconnect;
2486 return $result;
2487 }
2489 sub get_fai_types {
2490 my $tmp_classes = shift || return undef;
2491 my @result;
2493 foreach my $type(keys %{$tmp_classes}) {
2494 if(defined($tmp_classes->{$type}[0]) && (!($tmp_classes->{$type}[0] =~ /^.*?removed.*?$/))) {
2495 my $entry = {
2496 type => $type,
2497 state => $tmp_classes->{$type}[0],
2498 };
2499 push @result, $entry;
2500 }
2501 }
2503 return @result;
2504 }
2506 sub get_fai_state {
2507 my $result = "";
2508 my $tmp_classes = shift || return $result;
2510 foreach my $type(keys %{$tmp_classes}) {
2511 if(defined($tmp_classes->{$type}[0])) {
2512 $result = $tmp_classes->{$type}[0];
2514 # State is equal for all types in class
2515 last;
2516 }
2517 }
2519 return $result;
2520 }
2522 sub resolve_fai_classes {
2523 my ($fai_base, $ldap_handle, $session_id) = @_;
2524 if (not defined $session_id) { $session_id = 0; }
2525 my $result;
2526 my @possible_fai_classes= ("FAIscript", "FAIhook", "FAIpartitionTable", "FAItemplate", "FAIvariable", "FAIprofile", "FAIpackageList");
2527 my $fai_filter= "(|(&(objectClass=FAIclass)(|(objectClass=".join(")(objectClass=", @possible_fai_classes).")))(objectClass=FAIbranch))";
2528 my $fai_classes;
2530 daemon_log("$session_id DEBUG: Searching for FAI entries in base $fai_base",7);
2531 my $mesg= $ldap_handle->search(
2532 base => $fai_base,
2533 scope => 'sub',
2534 attrs => ['cn','objectClass','FAIstate'],
2535 filter => $fai_filter,
2536 );
2537 daemon_log("$session_id DEBUG: Found ".$mesg->count()." FAI entries",7);
2539 if($mesg->{'resultCode'} == 0 &&
2540 $mesg->count != 0) {
2541 foreach my $entry (@{$mesg->{entries}}) {
2542 if($entry->exists('cn')) {
2543 my $tmp_dn= $entry->dn();
2545 # Skip classname and ou dn parts for class
2546 my $tmp_release = ($1) if $tmp_dn =~ /^[^,]+,[^,]+,(.*?),$fai_base$/;
2548 # Skip classes without releases
2549 if((!defined($tmp_release)) || length($tmp_release)==0) {
2550 next;
2551 }
2553 my $tmp_cn= $entry->get_value('cn');
2554 my $tmp_state= $entry->get_value('FAIstate');
2556 my $tmp_type;
2557 # Get FAI type
2558 for my $oclass(@{$entry->get_value('objectClass', asref => 1)}) {
2559 if(grep $_ eq $oclass, @possible_fai_classes) {
2560 $tmp_type= $oclass;
2561 last;
2562 }
2563 }
2565 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
2566 # A Subrelease
2567 my @sub_releases = split(/,/, $tmp_release);
2569 # Walk through subreleases and build hash tree
2570 my $hash;
2571 while(my $tmp_sub_release = pop @sub_releases) {
2572 $hash .= "\{'$tmp_sub_release'\}->";
2573 }
2574 eval('push @{$fai_classes->'.$hash.'{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";');
2575 } else {
2576 # A branch, no subrelease
2577 push @{$fai_classes->{$tmp_release}->{$tmp_cn}->{$tmp_type}}, (defined($tmp_state) && length($tmp_state)>0)?$tmp_state:"";
2578 }
2579 } elsif (!$entry->exists('cn')) {
2580 my $tmp_dn= $entry->dn();
2581 my $tmp_release = ($1) if $tmp_dn =~ /^(.*?),$fai_base$/;
2583 # Skip classes without releases
2584 if((!defined($tmp_release)) || length($tmp_release)==0) {
2585 next;
2586 }
2588 if($tmp_release =~ /^.*?,.*?$/ && (!($tmp_release =~ /^.*?\\,.*?$/))) {
2589 # A Subrelease
2590 my @sub_releases= split(/,/, $tmp_release);
2592 # Walk through subreleases and build hash tree
2593 my $hash;
2594 while(my $tmp_sub_release = pop @sub_releases) {
2595 $hash .= "\{'$tmp_sub_release'\}->";
2596 }
2597 # Remove the last two characters
2598 chop($hash);
2599 chop($hash);
2601 eval('$fai_classes->'.$hash.'= {}');
2602 } else {
2603 # A branch, no subrelease
2604 if(!exists($fai_classes->{$tmp_release})) {
2605 $fai_classes->{$tmp_release} = {};
2606 }
2607 }
2608 }
2609 }
2611 # The hash is complete, now we can honor the copy-on-write based missing entries
2612 foreach my $release (keys %$fai_classes) {
2613 $result->{$release}= deep_copy(apply_fai_inheritance($fai_classes->{$release}));
2614 }
2615 }
2616 return $result;
2617 }
2619 sub apply_fai_inheritance {
2620 my $fai_classes = shift || return {};
2621 my $tmp_classes;
2623 # Get the classes from the branch
2624 foreach my $class (keys %{$fai_classes}) {
2625 # Skip subreleases
2626 if($class =~ /^ou=.*$/) {
2627 next;
2628 } else {
2629 $tmp_classes->{$class}= deep_copy($fai_classes->{$class});
2630 }
2631 }
2633 # Apply to each subrelease
2634 foreach my $subrelease (keys %{$fai_classes}) {
2635 if($subrelease =~ /ou=/) {
2636 foreach my $tmp_class (keys %{$tmp_classes}) {
2637 if(!exists($fai_classes->{$subrelease}->{$tmp_class})) {
2638 $fai_classes->{$subrelease}->{$tmp_class} =
2639 deep_copy($tmp_classes->{$tmp_class});
2640 } else {
2641 foreach my $type (keys %{$tmp_classes->{$tmp_class}}) {
2642 if(!exists($fai_classes->{$subrelease}->{$tmp_class}->{$type})) {
2643 $fai_classes->{$subrelease}->{$tmp_class}->{$type}=
2644 deep_copy($tmp_classes->{$tmp_class}->{$type});
2645 }
2646 }
2647 }
2648 }
2649 }
2650 }
2652 # Find subreleases in deeper levels
2653 foreach my $subrelease (keys %{$fai_classes}) {
2654 if($subrelease =~ /ou=/) {
2655 foreach my $subsubrelease (keys %{$fai_classes->{$subrelease}}) {
2656 if($subsubrelease =~ /ou=/) {
2657 apply_fai_inheritance($fai_classes->{$subrelease});
2658 }
2659 }
2660 }
2661 }
2663 return $fai_classes;
2664 }
2666 sub get_fai_release_entries {
2667 my $tmp_classes = shift || return;
2668 my $parent = shift || "";
2669 my @result = shift || ();
2671 foreach my $entry (keys %{$tmp_classes}) {
2672 if(defined($entry)) {
2673 if($entry =~ /^ou=.*$/) {
2674 my $release_name = $entry;
2675 $release_name =~ s/ou=//g;
2676 if(length($parent)>0) {
2677 $release_name = $parent."/".$release_name;
2678 }
2679 my @bufentries = get_fai_release_entries($tmp_classes->{$entry}, $release_name, @result);
2680 foreach my $bufentry(@bufentries) {
2681 push @result, $bufentry;
2682 }
2683 } else {
2684 my @types = get_fai_types($tmp_classes->{$entry});
2685 foreach my $type (@types) {
2686 push @result,
2687 {
2688 'class' => $entry,
2689 'type' => $type->{'type'},
2690 'release' => $parent,
2691 'state' => $type->{'state'},
2692 };
2693 }
2694 }
2695 }
2696 }
2698 return @result;
2699 }
2701 sub deep_copy {
2702 my $this = shift;
2703 if (not ref $this) {
2704 $this;
2705 } elsif (ref $this eq "ARRAY") {
2706 [map deep_copy($_), @$this];
2707 } elsif (ref $this eq "HASH") {
2708 +{map { $_ => deep_copy($this->{$_}) } keys %$this};
2709 } else { die "what type is $_?" }
2710 }
2713 sub session_run_result {
2714 my ($kernel, $heap, $client_answer) = @_[KERNEL, HEAP, ARG0];
2715 $kernel->sig(CHLD => "child_reap");
2716 }
2718 sub session_run_debug {
2719 my $result = $_[ARG0];
2720 print STDERR "$result\n";
2721 }
2723 sub session_run_done {
2724 my ( $kernel, $heap, $task_id ) = @_[ KERNEL, HEAP, ARG0 ];
2725 delete $heap->{task}->{$task_id};
2726 }
2729 sub create_sources_list {
2730 my $session_id = shift;
2731 my $ldap_handle = &main::get_ldap_handle;
2732 my $result="/tmp/gosa_si_tmp_sources_list";
2734 # Remove old file
2735 if(stat($result)) {
2736 unlink($result);
2737 &main::daemon_log("$session_id DEBUG: remove an old version of '$result'", 7);
2738 }
2740 my $fh;
2741 open($fh, ">$result");
2742 if (not defined $fh) {
2743 &main::daemon_log("$session_id DEBUG: cannot open '$result' for writing", 7);
2744 return undef;
2745 }
2746 if(defined($main::ldap_server_dn) and length($main::ldap_server_dn) > 0) {
2747 my $mesg=$ldap_handle->search(
2748 base => $main::ldap_server_dn,
2749 scope => 'base',
2750 attrs => 'FAIrepository',
2751 filter => 'objectClass=FAIrepositoryServer'
2752 );
2753 if($mesg->count) {
2754 foreach my $entry(@{$mesg->{'entries'}}) {
2755 foreach my $value(@{$entry->get_value('FAIrepository', asref => 1)}) {
2756 my ($server, $tag, $release, $sections)= split /\|/, $value;
2757 my $line = "deb $server $release";
2758 $sections =~ s/,/ /g;
2759 $line.= " $sections";
2760 print $fh $line."\n";
2761 }
2762 }
2763 }
2764 } else {
2765 if (defined $main::ldap_server_dn){
2766 &main::daemon_log("$session_id ERROR: something wrong with ldap_server_dn '$main::ldap_server_dn', abort create_sources_list", 1);
2767 } else {
2768 &main::daemon_log("$session_id ERROR: no ldap_server_dn found, abort create_sources_list", 1);
2769 }
2770 }
2771 close($fh);
2773 return $result;
2774 }
2777 sub run_create_packages_list_db {
2778 my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP];
2779 my $session_id = $session->ID;
2781 my $task = POE::Wheel::Run->new(
2782 Priority => +20,
2783 Program => sub {&create_packages_list_db(undef, undef, $session_id)},
2784 StdoutEvent => "session_run_result",
2785 StderrEvent => "session_run_debug",
2786 CloseEvent => "session_run_done",
2787 );
2788 $heap->{task}->{ $task->ID } = $task;
2789 }
2792 sub create_packages_list_db {
2793 my ($ldap_handle, $sources_file, $session_id) = @_;
2795 # it should not be possible to trigger a recreation of packages_list_db
2796 # while packages_list_db is under construction, so set flag packages_list_under_construction
2797 # which is tested befor recreation can be started
2798 if (-r $packages_list_under_construction) {
2799 daemon_log("$session_id WARNING: packages_list_db is right now under construction, please wait until this process is finished", 3);
2800 return;
2801 } else {
2802 daemon_log("$session_id INFO: create_packages_list_db: start", 5);
2803 # set packages_list_under_construction to true
2804 system("touch $packages_list_under_construction");
2805 @packages_list_statements=();
2806 }
2808 if (not defined $session_id) { $session_id = 0; }
2809 if (not defined $ldap_handle) {
2810 $ldap_handle= &get_ldap_handle();
2812 if (not defined $ldap_handle) {
2813 daemon_log("$session_id ERROR: no ldap_handle available to create_packages_list_db", 1);
2814 unlink($packages_list_under_construction);
2815 return;
2816 }
2817 }
2818 if (not defined $sources_file) {
2819 &main::daemon_log("$session_id INFO: no sources_file given for creating packages list so trigger creation of it", 5);
2820 $sources_file = &create_sources_list($session_id);
2821 }
2823 if (not defined $sources_file) {
2824 &main::daemon_log("$session_id ERROR: no sources_file given under '$sources_file', skip create_packages_list_db", 1);
2825 unlink($packages_list_under_construction);
2826 return;
2827 }
2829 my $line;
2831 open(CONFIG, "<$sources_file") or do {
2832 daemon_log( "$session_id ERROR: create_packages_list_db: Failed to open '$sources_file'", 1);
2833 unlink($packages_list_under_construction);
2834 return;
2835 };
2837 # Read lines
2838 while ($line = <CONFIG>){
2839 # Unify
2840 chop($line);
2841 $line =~ s/^\s+//;
2842 $line =~ s/^\s+/ /;
2844 # Strip comments
2845 $line =~ s/#.*$//g;
2847 # Skip empty lines
2848 if ($line =~ /^\s*$/){
2849 next;
2850 }
2852 # Interpret deb line
2853 if ($line =~ /^deb [^\s]+\s[^\s]+\s[^\s]+/){
2854 my( $baseurl, $dist, $sections ) = ($line =~ /^deb\s([^\s]+)\s+([^\s]+)\s+(.*)$/);
2855 my $section;
2856 foreach $section (split(' ', $sections)){
2857 &parse_package_info( $baseurl, $dist, $section, $session_id );
2858 }
2859 }
2860 }
2862 close (CONFIG);
2864 if(keys(%repo_dirs)) {
2865 find(\&cleanup_and_extract, keys( %repo_dirs ));
2866 &main::strip_packages_list_statements();
2867 $packages_list_db->exec_statementlist(\@packages_list_statements);
2868 }
2869 unlink($packages_list_under_construction);
2870 daemon_log("$session_id INFO: create_packages_list_db: finished", 5);
2871 return;
2872 }
2874 # This function should do some intensive task to minimize the db-traffic
2875 sub strip_packages_list_statements {
2876 my @existing_entries= @{$packages_list_db->exec_statement("SELECT * FROM $main::packages_list_tn")};
2877 my @new_statement_list=();
2878 my $hash;
2879 my $insert_hash;
2880 my $update_hash;
2881 my $delete_hash;
2882 my $known_packages_hash;
2883 my $local_timestamp=get_time();
2885 foreach my $existing_entry (@existing_entries) {
2886 $hash->{@{$existing_entry}[0]}->{@{$existing_entry}[1]}->{@{$existing_entry}[2]}= $existing_entry;
2887 }
2889 foreach my $statement (@packages_list_statements) {
2890 if($statement =~ /^INSERT/i) {
2891 # Assign the values from the insert statement
2892 my ($distribution,$package,$version,$section,$description,$template,$timestamp) = ($1,$2,$3,$4,$5,$6,$7) if $statement =~
2893 /^INSERT\s+?INTO\s+?$main::packages_list_tn\s+?VALUES\s*?\('(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)',\s*?'(.*?)'\s*?\)$/si;
2894 if(exists($hash->{$distribution}->{$package}->{$version})) {
2895 # If section or description has changed, update the DB
2896 if(
2897 (! (@{$hash->{$distribution}->{$package}->{$version}}[3] eq $section)) or
2898 (! (@{$hash->{$distribution}->{$package}->{$version}}[4] eq $description))
2899 ) {
2900 @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,undef);
2901 } else {
2902 # package is already present in database. cache this knowledge for later use
2903 @{$known_packages_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
2904 }
2905 } else {
2906 # Insert a non-existing entry to db
2907 @{$insert_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
2908 }
2909 } elsif ($statement =~ /^UPDATE/i) {
2910 my ($template,$package,$version) = ($1,$2,$3) if $statement =~
2911 /^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;
2912 foreach my $distribution (keys %{$hash}) {
2913 if(exists($insert_hash->{$distribution}->{$package}->{$version})) {
2914 # update the insertion hash to execute only one query per package (insert instead insert+update)
2915 @{$insert_hash->{$distribution}->{$package}->{$version}}[5]= $template;
2916 } elsif(exists($hash->{$distribution}->{$package}->{$version})) {
2917 if( ! (@{$hash->{$distribution}->{$package}->{$version}}[5] eq $template)) {
2918 my $section;
2919 my $description;
2920 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) and
2921 length(@{$update_hash->{$distribution}->{$package}->{$version}}[3]) > 0 ) {
2922 $section= @{$update_hash->{$distribution}->{$package}->{$version}}[3];
2923 }
2924 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
2925 $description= @{$update_hash->{$distribution}->{$package}->{$version}}[4];
2926 }
2927 @{$update_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section,$description,$template);
2928 }
2929 }
2930 }
2931 }
2932 }
2934 # Check for orphaned entries
2935 foreach my $existing_entry (@existing_entries) {
2936 my $distribution= @{$existing_entry}[0];
2937 my $package= @{$existing_entry}[1];
2938 my $version= @{$existing_entry}[2];
2939 my $section= @{$existing_entry}[3];
2941 if(
2942 exists($insert_hash->{$distribution}->{$package}->{$version}) ||
2943 exists($update_hash->{$distribution}->{$package}->{$version}) ||
2944 exists($known_packages_hash->{$distribution}->{$package}->{$version})
2945 ) {
2946 next;
2947 } else {
2948 # Insert entry to delete hash
2949 @{$delete_hash->{$distribution}->{$package}->{$version}} = ($distribution,$package,$version,$section);
2950 }
2951 }
2953 # unroll the insert hash
2954 foreach my $distribution (keys %{$insert_hash}) {
2955 foreach my $package (keys %{$insert_hash->{$distribution}}) {
2956 foreach my $version (keys %{$insert_hash->{$distribution}->{$package}}) {
2957 push @new_statement_list, "INSERT INTO $main::packages_list_tn VALUES ('$distribution','$package','$version',"
2958 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[3]',"
2959 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[4]',"
2960 ."'@{$insert_hash->{$distribution}->{$package}->{$version}}[5]',"
2961 ."'$local_timestamp')";
2962 }
2963 }
2964 }
2966 # unroll the update hash
2967 foreach my $distribution (keys %{$update_hash}) {
2968 foreach my $package (keys %{$update_hash->{$distribution}}) {
2969 foreach my $version (keys %{$update_hash->{$distribution}->{$package}}) {
2970 my $set = "";
2971 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[3])) {
2972 $set .= "section = '@{$update_hash->{$distribution}->{$package}->{$version}}[3]', ";
2973 }
2974 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[4])) {
2975 $set .= "description = '@{$update_hash->{$distribution}->{$package}->{$version}}[4]', ";
2976 }
2977 if(defined(@{$update_hash->{$distribution}->{$package}->{$version}}[5])) {
2978 $set .= "template = '@{$update_hash->{$distribution}->{$package}->{$version}}[5]', ";
2979 }
2980 if(defined($set) and length($set) > 0) {
2981 $set .= "timestamp = '$local_timestamp'";
2982 } else {
2983 next;
2984 }
2985 push @new_statement_list,
2986 "UPDATE $main::packages_list_tn SET $set WHERE"
2987 ." distribution = '$distribution'"
2988 ." AND package = '$package'"
2989 ." AND version = '$version'";
2990 }
2991 }
2992 }
2994 # unroll the delete hash
2995 foreach my $distribution (keys %{$delete_hash}) {
2996 foreach my $package (keys %{$delete_hash->{$distribution}}) {
2997 foreach my $version (keys %{$delete_hash->{$distribution}->{$package}}) {
2998 my $section = @{$delete_hash->{$distribution}->{$package}->{$version}}[3];
2999 push @new_statement_list, "DELETE FROM $main::packages_list_tn WHERE distribution='$distribution' AND package='$package' AND version='$version' AND section='$section'";
3000 }
3001 }
3002 }
3004 @packages_list_statements = @new_statement_list;
3005 }
3008 sub parse_package_info {
3009 my ($baseurl, $dist, $section, $session_id)= @_;
3010 my ($package);
3011 if (not defined $session_id) { $session_id = 0; }
3012 my ($path) = ($baseurl =~ m%://[^/]*(.*)$%);
3013 $repo_dirs{ "${repo_path}/pool" } = 1;
3015 foreach $package ("Packages.gz"){
3016 daemon_log("$session_id DEBUG: create_packages_list: fetch $baseurl, $dist, $section", 7);
3017 get_package( "$baseurl/dists/$dist/$section/binary-$arch/$package", "$outdir/$dist/$section", $session_id );
3018 parse_package( "$outdir/$dist/$section", $dist, $path, $session_id );
3019 }
3021 }
3024 sub get_package {
3025 my ($url, $dest, $session_id)= @_;
3026 if (not defined $session_id) { $session_id = 0; }
3028 my $tpath = dirname($dest);
3029 -d "$tpath" || mkpath "$tpath";
3031 # This is ugly, but I've no time to take a look at "how it works in perl"
3032 if(0 == system("wget '$url' -O '$dest' 2>/dev/null") ) {
3033 system("gunzip -cd '$dest' > '$dest.in'");
3034 daemon_log("$session_id DEBUG: run command: gunzip -cd '$dest' > '$dest.in'", 5);
3035 unlink($dest);
3036 daemon_log("$session_id DEBUG: delete file '$dest'", 5);
3037 } else {
3038 daemon_log("$session_id ERROR: create_packages_list_db: get_packages: fetching '$url' failed!", 1);
3039 }
3040 return 0;
3041 }
3044 sub parse_package {
3045 my ($path, $dist, $srv_path, $session_id)= @_;
3046 if (not defined $session_id) { $session_id = 0;}
3047 my ($package, $version, $section, $description);
3048 my $PACKAGES;
3049 my $timestamp = &get_time();
3051 if(not stat("$path.in")) {
3052 daemon_log("$session_id ERROR: create_packages_list: parse_package: file '$path.in' is not readable",1);
3053 return;
3054 }
3056 open($PACKAGES, "<$path.in");
3057 if(not defined($PACKAGES)) {
3058 daemon_log("$session_id ERROR: create_packages_list_db: parse_package: cannot open '$path.in'",1);
3059 return;
3060 }
3062 # Read lines
3063 while (<$PACKAGES>){
3064 my $line = $_;
3065 # Unify
3066 chop($line);
3068 # Use empty lines as a trigger
3069 if ($line =~ /^\s*$/){
3070 my $sql = "INSERT INTO packages_list VALUES ('$dist', '$package', '$version', '$section', '$description', '', '$timestamp')";
3071 push(@packages_list_statements, $sql);
3072 $package = "none";
3073 $version = "none";
3074 $section = "none";
3075 $description = "none";
3076 next;
3077 }
3079 # Trigger for package name
3080 if ($line =~ /^Package:\s/){
3081 ($package)= ($line =~ /^Package: (.*)$/);
3082 next;
3083 }
3085 # Trigger for version
3086 if ($line =~ /^Version:\s/){
3087 ($version)= ($line =~ /^Version: (.*)$/);
3088 next;
3089 }
3091 # Trigger for description
3092 if ($line =~ /^Description:\s/){
3093 ($description)= &encode_base64(($line =~ /^Description: (.*)$/));
3094 next;
3095 }
3097 # Trigger for section
3098 if ($line =~ /^Section:\s/){
3099 ($section)= ($line =~ /^Section: (.*)$/);
3100 next;
3101 }
3103 # Trigger for filename
3104 if ($line =~ /^Filename:\s/){
3105 my ($filename) = ($line =~ /^Filename: (.*)$/);
3106 store_fileinfo( $package, $filename, $dist, $srv_path, $version, $repo_path );
3107 next;
3108 }
3109 }
3111 close( $PACKAGES );
3112 unlink( "$path.in" );
3113 }
3116 sub store_fileinfo {
3117 my( $package, $file, $dist, $path, $vers, $srvdir) = @_;
3119 my %fileinfo = (
3120 'package' => $package,
3121 'dist' => $dist,
3122 'version' => $vers,
3123 );
3125 $repo_files{ "${srvdir}/$file" } = \%fileinfo;
3126 }
3129 sub cleanup_and_extract {
3130 my $fileinfo = $repo_files{ $File::Find::name };
3132 if( defined $fileinfo ) {
3133 my $dir = "$outdir/$fileinfo->{ 'dist' }/debconf.d";
3134 my $sql;
3135 my $package = $fileinfo->{ 'package' };
3136 my $newver = $fileinfo->{ 'version' };
3138 mkpath($dir);
3139 system( "dpkg -e '$File::Find::name' '$dir/DEBIAN'" );
3141 if( -f "$dir/DEBIAN/templates" ) {
3143 daemon_log("0 DEBUG: Found debconf templates in '$package' - $newver", 7);
3145 my $tmpl= ""; {
3146 local $/=undef;
3147 open FILE, "$dir/DEBIAN/templates";
3148 $tmpl = &encode_base64(<FILE>);
3149 close FILE;
3150 }
3151 rmtree("$dir/DEBIAN/templates");
3153 $sql= "update $main::packages_list_tn set template = '$tmpl' where package = '$package' and version = '$newver';";
3154 push @packages_list_statements, $sql;
3155 }
3156 }
3158 return;
3159 }
3162 sub register_at_foreign_servers {
3163 my ($kernel) = $_[KERNEL];
3165 # hole alle bekannten server aus known_server_db
3166 my $server_sql = "SELECT * FROM $known_server_tn";
3167 my $server_res = $known_server_db->exec_statement($server_sql);
3169 # no entries in known_server_db
3170 if (not ref(@$server_res[0]) eq "ARRAY") {
3171 # TODO
3172 }
3174 # detect already connected clients
3175 my $client_sql = "SELECT * FROM $known_clients_tn";
3176 my $client_res = $known_clients_db->exec_statement($client_sql);
3178 # send my server details to all other gosa-si-server within the network
3179 foreach my $hit (@$server_res) {
3180 my $hostname = @$hit[0];
3181 my $hostkey = &create_passwd;
3183 # add already connected clients to registration message
3184 my $myhash = &create_xml_hash('new_server', $server_address, $hostname);
3185 &add_content2xml_hash($myhash, 'key', $hostkey);
3186 map(&add_content2xml_hash($myhash, 'client', @{$_}[0].",".@{$_}[4]), @$client_res);
3188 # add locally loaded gosa-si modules to registration message
3189 my $loaded_modules = {};
3190 while (my ($package, $pck_info) = each %$known_modules) {
3191 next if ((!defined(@$pck_info[2])) || (!(ref (@$pck_info[2]) eq 'HASH')));
3192 foreach my $act_module (keys(%{@$pck_info[2]})) {
3193 $loaded_modules->{$act_module} = "";
3194 }
3195 }
3197 map(&add_content2xml_hash($myhash, "loaded_modules", $_), keys(%$loaded_modules));
3199 # add macaddress to registration message
3200 my ($host_ip, $host_port) = split(/:/, $hostname);
3201 my $local_ip = &get_local_ip_for_remote_ip($host_ip);
3202 my $network_interface= &get_interface_for_ip($local_ip);
3203 my $host_mac = &get_mac_for_interface($network_interface);
3204 &add_content2xml_hash($myhash, 'macaddress', $host_mac);
3206 # build registration message and send it
3207 my $foreign_server_msg = &create_xml_string($myhash);
3208 my $error = &send_msg_to_target($foreign_server_msg, $hostname, $ServerPackages_key, "new_server", 0);
3209 }
3211 $kernel->delay_set("register_at_foreign_servers", $foreign_servers_register_delay);
3212 return;
3213 }
3216 #==== MAIN = main ==============================================================
3217 # parse commandline options
3218 Getopt::Long::Configure( "bundling" );
3219 GetOptions("h|help" => \&usage,
3220 "c|config=s" => \$cfg_file,
3221 "f|foreground" => \$foreground,
3222 "v|verbose+" => \$verbose,
3223 "no-arp+" => \$no_arp,
3224 );
3226 # read and set config parameters
3227 &check_cmdline_param ;
3228 &read_configfile($cfg_file, %cfg_defaults);
3229 &check_pid;
3231 $SIG{CHLD} = 'IGNORE';
3233 # forward error messages to logfile
3234 if( ! $foreground ) {
3235 open( STDIN, '+>/dev/null' );
3236 open( STDOUT, '+>&STDIN' );
3237 open( STDERR, '+>&STDIN' );
3238 }
3240 # Just fork, if we are not in foreground mode
3241 if( ! $foreground ) {
3242 chdir '/' or die "Can't chdir to /: $!";
3243 $pid = fork;
3244 setsid or die "Can't start a new session: $!";
3245 umask 0;
3246 } else {
3247 $pid = $$;
3248 }
3250 # Do something useful - put our PID into the pid_file
3251 if( 0 != $pid ) {
3252 open( LOCK_FILE, ">$pid_file" );
3253 print LOCK_FILE "$pid\n";
3254 close( LOCK_FILE );
3255 if( !$foreground ) {
3256 exit( 0 )
3257 };
3258 }
3260 # parse head url and revision from svn
3261 my $server_status_hash = { 'developmental'=>'revision', 'stable'=>'release'};
3262 $server_version =~ /^\$HeadURL: (\S+) \$:\$Rev: (\d+) \$$/;
3263 $server_headURL = defined $1 ? $1 : 'unknown' ;
3264 $server_revision = defined $2 ? $2 : 'unknown' ;
3265 if ($server_headURL =~ /\/tag\// ||
3266 $server_headURL =~ /\/branches\// ) {
3267 $server_status = "stable";
3268 } else {
3269 $server_status = "developmental" ;
3270 }
3272 # Prepare log file
3273 $root_uid = getpwnam('root');
3274 $adm_gid = getgrnam('adm');
3275 chmod(0640, $log_file);
3276 chown($root_uid, $adm_gid, $log_file);
3277 chown($root_uid, $adm_gid, "/var/lib/gosa-si");
3279 daemon_log(" ", 1);
3280 daemon_log("$0 started!", 1);
3281 daemon_log("status: $server_status", 1);
3282 daemon_log($server_status_hash->{$server_status}.": $server_revision", 1);
3284 {
3285 no strict "refs";
3287 if ($db_module eq "DBmysql") {
3288 # connect to incoming_db
3289 $incoming_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3291 # connect to gosa-si job queue
3292 $job_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3294 # connect to known_clients_db
3295 $known_clients_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3297 # connect to foreign_clients_db
3298 $foreign_clients_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3300 # connect to known_server_db
3301 $known_server_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3303 # connect to login_usr_db
3304 $login_users_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3306 # connect to fai_server_db
3307 $fai_server_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3309 # connect to fai_release_db
3310 $fai_release_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3312 # connect to packages_list_db
3313 $packages_list_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3315 # connect to messaging_db
3316 $messaging_db = ("GOSA::".$db_module)->new($main::mysql_database, $main::mysql_host, $main::mysql_username, $main::mysql_password);
3318 } elsif ($db_module eq "DBsqlite") {
3319 # connect to incoming_db
3320 unlink($incoming_file_name);
3321 $incoming_db = GOSA::DBsqlite->new($incoming_file_name);
3323 # connect to gosa-si job queue
3324 unlink($job_queue_file_name); ## just for debugging
3325 $job_db = GOSA::DBsqlite->new($job_queue_file_name);
3326 chmod(0660, $job_queue_file_name);
3327 chown($root_uid, $adm_gid, $job_queue_file_name);
3329 # connect to known_clients_db
3330 unlink($known_clients_file_name); ## just for debugging
3331 $known_clients_db = GOSA::DBsqlite->new($known_clients_file_name);
3332 chmod(0660, $known_clients_file_name);
3333 chown($root_uid, $adm_gid, $known_clients_file_name);
3335 # connect to foreign_clients_db
3336 unlink($foreign_clients_file_name);
3337 $foreign_clients_db = GOSA::DBsqlite->new($foreign_clients_file_name);
3338 chmod(0660, $foreign_clients_file_name);
3339 chown($root_uid, $adm_gid, $foreign_clients_file_name);
3341 # connect to known_server_db
3342 unlink($known_server_file_name);
3343 $known_server_db = GOSA::DBsqlite->new($known_server_file_name);
3344 chmod(0660, $known_server_file_name);
3345 chown($root_uid, $adm_gid, $known_server_file_name);
3347 # connect to login_usr_db
3348 unlink($login_users_file_name);
3349 $login_users_db = GOSA::DBsqlite->new($login_users_file_name);
3350 chmod(0660, $login_users_file_name);
3351 chown($root_uid, $adm_gid, $login_users_file_name);
3353 # connect to fai_server_db
3354 unlink($fai_server_file_name);
3355 $fai_server_db = GOSA::DBsqlite->new($fai_server_file_name);
3356 chmod(0660, $fai_server_file_name);
3357 chown($root_uid, $adm_gid, $fai_server_file_name);
3359 # connect to fai_release_db
3360 unlink($fai_release_file_name);
3361 $fai_release_db = GOSA::DBsqlite->new($fai_release_file_name);
3362 chmod(0660, $fai_release_file_name);
3363 chown($root_uid, $adm_gid, $fai_release_file_name);
3365 # connect to packages_list_db
3366 #unlink($packages_list_file_name);
3367 unlink($packages_list_under_construction);
3368 $packages_list_db = GOSA::DBsqlite->new($packages_list_file_name);
3369 chmod(0660, $packages_list_file_name);
3370 chown($root_uid, $adm_gid, $packages_list_file_name);
3372 # connect to messaging_db
3373 unlink($messaging_file_name);
3374 $messaging_db = GOSA::DBsqlite->new($messaging_file_name);
3375 chmod(0660, $messaging_file_name);
3376 chown($root_uid, $adm_gid, $messaging_file_name);
3377 }
3378 }
3380 # Creating tables
3381 $messaging_db->create_table($messaging_tn, \@messaging_col_names);
3382 $packages_list_db->create_table($packages_list_tn, \@packages_list_col_names);
3383 $fai_release_db->create_table($fai_release_tn, \@fai_release_col_names);
3384 $fai_server_db->create_table($fai_server_tn, \@fai_server_col_names);
3385 $login_users_db->create_table($login_users_tn, \@login_users_col_names);
3386 $known_server_db->create_table($known_server_tn, \@known_server_col_names);
3387 $foreign_clients_db->create_table($foreign_clients_tn, \@foreign_clients_col_names);
3388 $known_clients_db->create_table($known_clients_tn, \@known_clients_col_names);
3389 $incoming_db->create_table($incoming_tn, \@incoming_col_names);
3390 $job_db->create_table($job_queue_tn, \@job_queue_col_names);
3393 # create xml object used for en/decrypting
3394 $xml = new XML::Simple();
3397 # foreign servers
3398 my @foreign_server_list;
3400 # add foreign server from cfg file
3401 if ($foreign_server_string ne "") {
3402 my @cfg_foreign_server_list = split(",", $foreign_server_string);
3403 foreach my $foreign_server (@cfg_foreign_server_list) {
3404 push(@foreign_server_list, $foreign_server);
3405 }
3407 daemon_log("0 INFO: found foreign server in config file: ".join(", ", @foreign_server_list), 5);
3408 }
3410 # Perform a DNS lookup for server registration if flag is true
3411 if ($dns_lookup eq "true") {
3412 # Add foreign server from dns
3413 my @tmp_servers;
3414 if (not $server_domain) {
3415 # Try our DNS Searchlist
3416 for my $domain(get_dns_domains()) {
3417 chomp($domain);
3418 my ($tmp_domains, $error_string) = &get_server_addresses($domain);
3419 if(@$tmp_domains) {
3420 for my $tmp_server(@$tmp_domains) {
3421 push @tmp_servers, $tmp_server;
3422 }
3423 }
3424 }
3425 if(@tmp_servers && length(@tmp_servers)==0) {
3426 daemon_log("0 WARNING: no foreign gosa-si-server found in DNS for domain '$server_domain'", 3);
3427 }
3428 } else {
3429 @tmp_servers = &get_server_addresses($server_domain);
3430 if( 0 == @tmp_servers ) {
3431 daemon_log("0 WARNING: no foreign gosa-si-server found in DNS for domain '$server_domain'", 3);
3432 }
3433 }
3435 daemon_log("0 INFO: found foreign server via DNS ".join(", ", @tmp_servers), 5);
3437 foreach my $server (@tmp_servers) {
3438 unshift(@foreign_server_list, $server);
3439 }
3440 } else {
3441 daemon_log("0 INFO: DNS lookup for server registration is disabled", 5);
3442 }
3445 # eliminate duplicate entries
3446 @foreign_server_list = &del_doubles(@foreign_server_list);
3447 my $all_foreign_server = join(", ", @foreign_server_list);
3448 daemon_log("0 INFO: found foreign server in config file and DNS: '$all_foreign_server'", 5);
3450 # add all found foreign servers to known_server
3451 my $act_timestamp = &get_time();
3452 foreach my $foreign_server (@foreign_server_list) {
3454 # do not add myself to known_server_db
3455 if (&is_local($foreign_server)) { next; }
3456 ######################################
3458 my $res = $known_server_db->add_dbentry( {table=>$known_server_tn,
3459 primkey=>['hostname'],
3460 hostname=>$foreign_server,
3461 macaddress=>"",
3462 status=>'not_jet_registered',
3463 hostkey=>"none",
3464 loaded_modules => "none",
3465 timestamp=>$act_timestamp,
3466 } );
3467 }
3470 # Import all modules
3471 &import_modules;
3473 # Check wether all modules are gosa-si valid passwd check
3474 &password_check;
3476 # Prepare for using Opsi
3477 if ($opsi_enabled eq "true") {
3478 use JSON::RPC::Client;
3479 use XML::Quote qw(:all);
3480 $opsi_url= "https://".$opsi_admin.":".$opsi_password."@".$opsi_server.":4447/rpc";
3481 $opsi_client = new JSON::RPC::Client;
3482 }
3485 POE::Component::Server::TCP->new(
3486 Alias => "TCP_SERVER",
3487 Port => $server_port,
3488 ClientInput => sub {
3489 my ($kernel, $input, $heap, $session) = @_[KERNEL, ARG0, HEAP, SESSION];
3490 my $session_id = $session->ID;
3491 my $remote_ip = $heap->{'remote_ip'};
3492 push(@msgs_to_decrypt, $input);
3493 &daemon_log("$session_id DEBUG: incoming message from '$remote_ip'", 7);
3494 $kernel->yield("msg_to_decrypt");
3495 },
3496 InlineStates => {
3497 msg_to_decrypt => \&msg_to_decrypt,
3498 next_task => \&next_task,
3499 task_result => \&handle_task_result,
3500 task_done => \&handle_task_done,
3501 task_debug => \&handle_task_debug,
3502 child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!" },
3503 }
3504 );
3506 daemon_log("0 INFO: start socket for incoming xml messages at port '$server_port' ", 1);
3508 # create session for repeatedly checking the job queue for jobs
3509 POE::Session->create(
3510 inline_states => {
3511 _start => \&session_start,
3512 register_at_foreign_servers => \®ister_at_foreign_servers,
3513 sig_handler => \&sig_handler,
3514 next_task => \&next_task,
3515 task_result => \&handle_task_result,
3516 task_done => \&handle_task_done,
3517 task_debug => \&handle_task_debug,
3518 watch_for_next_tasks => \&watch_for_next_tasks,
3519 watch_for_new_messages => \&watch_for_new_messages,
3520 watch_for_delivery_messages => \&watch_for_delivery_messages,
3521 watch_for_done_messages => \&watch_for_done_messages,
3522 watch_for_new_jobs => \&watch_for_new_jobs,
3523 watch_for_modified_jobs => \&watch_for_modified_jobs,
3524 watch_for_done_jobs => \&watch_for_done_jobs,
3525 watch_for_opsi_jobs => \&watch_for_opsi_jobs,
3526 watch_for_old_known_clients => \&watch_for_old_known_clients,
3527 create_packages_list_db => \&run_create_packages_list_db,
3528 create_fai_server_db => \&run_create_fai_server_db,
3529 create_fai_release_db => \&run_create_fai_release_db,
3530 recreate_packages_db => \&run_recreate_packages_db,
3531 session_run_result => \&session_run_result,
3532 session_run_debug => \&session_run_debug,
3533 session_run_done => \&session_run_done,
3534 child_reap => sub { "Do nothing special. I'm just a comment, but i'm necessary!" },
3535 }
3536 );
3539 POE::Kernel->run();
3540 exit;