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